@live-change/frontend-auto-form 0.9.84 → 0.9.86

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.
@@ -7,10 +7,41 @@
7
7
  Action <strong>{{ action }}</strong>
8
8
  </div>
9
9
 
10
- <form @submit="handleSubmit" @reset="handleReset">
10
+ <div v-if="task">
11
+ <Task :task="rootTask" :tasks="tasksData" />
12
+ <pre>rootTask = {{ rootTask }}</pre>
13
+ </div>
14
+ <div v-else-if="resultWithType">
15
+ <div class="text-xl mb-2">
16
+ Result:
17
+ </div>
18
+ <div class="text-sm text-gray-500">
19
+ <pre>{{ resultWithType }}</pre>
20
+ </div>
21
+ </div>
22
+ <form v-else @submit="handleSubmit" @reset="handleReset">
11
23
  <div class="flex flex-col gap-4">
24
+
25
+ <div v-for="[name, parameter] in Object.entries(actionFormData.parameters)"
26
+ class="flex flex-col mb-3">
27
+ <template v-if="!name.endsWith('Type')">
28
+ <div class="min-w-[8rem] font-medium">{{ actionFormData.action.definition.properties[name].label ?? name }}</div>
29
+ <div>
30
+ <InjectedObjectIndentification v-if="actionFormData.parameters[name+'Type']
31
+ ?? actionFormData.action.definition.properties[name]?.type
32
+ ?? actionFormData.action.definition.properties[name]?.type.split('_').length > 1"
33
+ :type="actionFormData.parameters[name+'Type']
34
+ ?? actionFormData.action.definition.properties[name]?.type"
35
+ :object="actionFormData.parameters[name]"
36
+ />
37
+ <pre v-else>parameter</pre>
38
+ </div>
39
+ </template>
40
+ </div>
41
+
12
42
  <auto-editor
13
43
  :definition="actionFormData.action.definition"
44
+ :editableProperties="actionFormData.editableProperties"
14
45
  v-model="actionFormData.value"
15
46
  :rootValue="actionFormData.value"
16
47
  :errors="actionFormData.propertiesErrors"
@@ -49,10 +80,14 @@
49
80
 
50
81
  <script setup>
51
82
 
52
- import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
83
+ import { ref, computed, onMounted, defineProps, defineEmits, toRefs, watch } from 'vue'
53
84
  import Button from 'primevue/button'
54
85
  import AutoEditor from '../form/AutoEditor.vue'
55
86
  import ActionButtons from './ActionButtons.vue'
87
+ import { Task } from '@live-change/task-frontend'
88
+
89
+ import InjectedObjectIndentification from './InjectedObjectIndentification.vue'
90
+
56
91
  import { useToast } from 'primevue/usetoast'
57
92
  const toast = useToast()
58
93
 
@@ -68,34 +103,128 @@
68
103
  i18n: {
69
104
  type: String,
70
105
  default: ''
106
+ },
107
+ initialValue: {
108
+ type: Object,
109
+ default: () => ({})
110
+ },
111
+ parameters: {
112
+ type: Object,
113
+ default: () => ({})
71
114
  }
72
115
  })
73
116
 
74
- const { service, action, i18n } = toRefs(props)
117
+ const { service, action, i18n, initialValue, parameters } = toRefs(props)
75
118
 
76
119
  const emit = defineEmits(['done', 'error'])
77
120
 
78
- import { useApi } from '@live-change/vue3-ssr'
121
+ import { useApi, usePath, live } from '@live-change/vue3-ssr'
79
122
  const api = useApi()
123
+ const path = usePath()
124
+
125
+ import { useRouter } from 'vue-router'
126
+ const router = useRouter()
80
127
 
81
128
  import { actionData } from '@live-change/frontend-auto-form'
82
129
 
83
- const actionFormData = await actionData({
130
+ const resultWithType = ref(null)
131
+ const task = ref(null)
132
+
133
+ const tasksPath = computed(() => task.value && path.task.tasksByRoot({
134
+ root: task.value,
135
+ rootType: 'task_Task'
136
+ }))
137
+
138
+ const actionFormDataPromise = actionData({
84
139
  service: service.value,
85
140
  action: action.value,
86
- i18n: i18n.value
141
+ i18n: i18n.value,
142
+ initialValue: initialValue.value,
143
+ parameters: parameters.value,
144
+ onDone: handleDone
145
+ })
146
+
147
+ const [
148
+ actionFormData,
149
+ tasksData
150
+ ] = await Promise.all([
151
+ actionFormDataPromise,
152
+ live(tasksPath)
153
+ ])
154
+
155
+ const returnType = computed(() => {
156
+ const returnType = actionFormData.action.definition.returns
157
+ return returnType.type
87
158
  })
88
159
 
89
- const handleSubmit = (ev) => {
160
+ function handleSubmit(ev) {
90
161
  ev.preventDefault()
91
162
  actionFormData.submit()
92
163
  }
93
164
 
94
- const handleReset = (ev) => {
165
+ function handleReset(ev) {
95
166
  ev.preventDefault()
96
167
  actionFormData.reset()
97
168
  }
98
169
 
170
+ function handleDone(result) {
171
+ console.log('handleDone', result)
172
+ handleResult(result, returnType.value)
173
+ }
174
+
175
+ function handleResult(result, type) {
176
+ console.log('handleResult', result, type)
177
+ task.value = null
178
+ resultWithType.value = null
179
+ if(type === 'task_Task') {
180
+ task.value = result
181
+ } else if(type.split('_').length === 1) {
182
+ if(typeof result === 'object') {
183
+ resultWithType.value = {
184
+ type: type,
185
+ result: result
186
+ }
187
+ } else { /// redirect to object view
188
+ router.push({
189
+ name: 'auto-form:view',
190
+ params: {
191
+ serviceName: service.value,
192
+ modelName: type.split('_')[0],
193
+ identifiers: [result]
194
+ }
195
+ })
196
+ }
197
+ } else {
198
+ resultWithType.value = {
199
+ type: type,
200
+ result: result
201
+ }
202
+ }
203
+ }
204
+
205
+ const rootTask = computed(() => {
206
+ if(task.value && tasksData.value) {
207
+ return tasksData.value.find(t => t.id === task.value)
208
+ }
209
+ })
210
+
211
+ const rootTaskDone = computed(() => {
212
+ if(rootTask.value) {
213
+ return rootTask.value.state === 'done'
214
+ }
215
+ return false
216
+ })
217
+
218
+ /* watch(rootTaskDone, (done) => {
219
+ if(done) {
220
+ const taskType = rootTask.value.type
221
+ const taskService = rootTask.value.service
222
+ const taskDefinition = api.serviceDefinition(taskService).tasks[taskType]
223
+ handleResult(rootTask.value.result, taskDefinition.returns.type)
224
+ }
225
+ }, { immediate: true }) */
226
+
227
+
99
228
  </script>
100
229
 
101
230
  <style scoped>
@@ -26,22 +26,30 @@
26
26
  <div class="">
27
27
  Service <strong>{{ service }}</strong>
28
28
  </div>
29
- <div class="text-2xl mb-6">
30
- <span v-if="isNew">Create </span>
31
- <span v-else>Edit </span>
32
- <strong>{{ model }}</strong>
29
+ <div class="flex flex-row flex-wrap justify-between align-items-top">
30
+ <div class="text-2xl mb-6">
31
+ <span v-if="isNew">Create </span>
32
+ <span v-else>Edit </span>
33
+ <strong>{{ model }}</strong>
34
+ </div>
35
+ <div v-if="!isNew" class="flex flex-row flex-wrap justify-between align-items-top gap-2">
36
+ <router-link :to="viewRoute">
37
+ <Button label="View" icon="pi pi-eye" class="p-button mb-6" />
38
+ </router-link>
39
+ </div>
33
40
  </div>
34
41
 
35
42
  <form v-if="editor" @submit="handleSave" @reset="handleReset">
36
- <div v-for="identifier in modelDefinition.identifiers">
43
+ <div v-for="identifier in modelDefinition.identifiers">
37
44
  <template v-if="(identifier.name ?? identifier).slice(-4) !== 'Type'">
38
- <div v-if="identifiers[identifier]" class="flex flex-col mb-3">
45
+ <div v-if="identifiers[identifier.name ?? identifier]" class="flex flex-col mb-3">
39
46
  <div class="min-w-[8rem] font-medium">{{ identifier.name ?? identifier }}</div>
40
47
  <div class="">
41
- <InjectedObjectIndentification
42
- :type="identifiers[(identifier.field ?? identifier)+'Type']
43
- ?? modelDefinition.properties[identifier.field ?? identifier].type"
44
- :object="identifiers[identifier.field ?? identifier]"
48
+ <InjectedObjectIndentification v-if="identifiers[(identifier.name ?? identifier)+'Type']
49
+ ?? modelDefinition.properties[identifier.field ?? identifier]?.type"
50
+ :type="identifiers[(identifier.name ?? identifier)+'Type']
51
+ ?? modelDefinition.properties[identifier.field ?? identifier]?.type"
52
+ :object="identifiers[identifier.name ?? identifier]"
45
53
  />
46
54
  </div>
47
55
  </div>
@@ -161,10 +169,20 @@
161
169
  console.log("SAVED", saveResult, isNew.value, editor.value.isNew)
162
170
  if(saveResult && isNew.value && editor.value.isNew) {
163
171
  emit('created', saveResult)
164
- }
165
- emit('saved', saveResult)
172
+ } else {
173
+ emit('saved', saveResult)
174
+ }
166
175
  }
167
176
 
177
+ const viewRoute = computed(() => ({
178
+ name: 'auto-form:editor',
179
+ params: {
180
+ serviceName: service.value,
181
+ modelName: model.value,
182
+ identifiers: Object.values(identifiers.value)
183
+ }
184
+ }))
185
+
168
186
  const scopesPath = computed(() => path.scope.objectScopes({
169
187
  objectType: parentObjects.value[0].objectType, /// TODO: support multiple parent objects!
170
188
  object: parentObjects.value[0].object
@@ -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
 
@@ -28,7 +19,12 @@
28
19
  class="ml-2"
29
20
  />
30
21
  </div>
31
- <Button label="Access" icon="pi pi-key" class="p-button mb-6" @click="showAccessControl" />
22
+ <div class="flex flex-row flex-wrap justify-between align-items-top gap-2">
23
+ <Button label="Access" icon="pi pi-key" class="p-button mb-6" @click="showAccessControl" />
24
+ <router-link :to="editRoute">
25
+ <Button label="Edit" icon="pi pi-pencil" class="p-button mb-6" />
26
+ </router-link>
27
+ </div>
32
28
  </div>
33
29
 
34
30
  <AutoView :value="object" :root-value="object" :i18n="i18n" :attributes="attributes"
@@ -36,6 +32,15 @@
36
32
 
37
33
  </div>
38
34
 
35
+ <div v-if="connectedActions" class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border mb-6">
36
+ <div v-for="action of connectedActions" class="mb-6">
37
+ <pre>{{ action }}</pre>
38
+ <router-link :to="actionRoute(action)">
39
+ <Button :label="action.label" icon="pi pi-play" class="p-button mb-6" />
40
+ </router-link>
41
+ </div>
42
+ </div>
43
+
39
44
  <div v-for="preparedRelation of visibleObjectRelations" class="mb-6">
40
45
  <ModelSingle :service="preparedRelation.service" :model="preparedRelation.model"
41
46
  :views="preparedRelation.views">
@@ -93,7 +98,7 @@
93
98
 
94
99
  <div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
95
100
 
96
- <pre>visibleRangeRelations = {{ visibleRangeRelations }}</pre>
101
+ <!-- <pre>visibleRangeRelations = {{ visibleRangeRelations }}</pre>
97
102
  <pre>preparedRelations = {{ preparedRelations }}</pre>
98
103
 
99
104
  <div v-if="backwardRelations">
@@ -104,9 +109,13 @@
104
109
  )
105
110
  }}</pre>
106
111
  </div>
112
+ <pre>accessControlRoles = {{ accessControlRoles }}</pre> -->
113
+
114
+ <pre>identifiers = {{ identifiers }}</pre>
107
115
 
116
+ <pre>definition = {{ modelDefinition }}</pre>
108
117
 
109
- <pre>accessControlRoles = {{ accessControlRoles }}</pre>
118
+ <pre>object = {{ object }}</pre>
110
119
 
111
120
  </div>
112
121
 
@@ -173,6 +182,24 @@
173
182
  return api.services?.[service.value]?.models?.[model.value]
174
183
  })
175
184
 
185
+ const connectedActions = computed(() => {
186
+ const srcActions = modelDefinition.value?.connectedActions
187
+ if(!srcActions) return null
188
+ return Object.values(srcActions).map(action => {
189
+ const config = {
190
+ service: service.value,
191
+ ...action
192
+ }
193
+ const actionDefinition = api.getServiceDefinition(config.service).actions[config.name]
194
+ const label = actionDefinition.label ?? action.label ?? actionDefinition.name
195
+ return {
196
+ ...config,
197
+ definition: actionDefinition,
198
+ label
199
+ }
200
+ })
201
+ })
202
+
176
203
  import { getForwardRelations, getBackwardRelations, anyRelationsTypes, prepareObjectRelations }
177
204
  from '../../logic/relations.js'
178
205
  const forwardRelations = computed(() => getForwardRelations(modelDefinition.value, () => true, api))
@@ -200,14 +227,18 @@
200
227
  return prepareObjectRelations(objectType.value, object.value.to ?? object.value.id, api)
201
228
  })
202
229
 
203
- const visibleRangeRelations = computed(() => preparedRelations.value.map(preparedRelation => {
204
- const accessibleViews = preparedRelation.views.filter(view => preparedRelation.access.value[view.name])
205
- if(accessibleViews.length === 0) return null
206
- return {
207
- ...preparedRelation,
208
- views: accessibleViews
209
- }
210
- }).filter(x => x !== null))
230
+ const visibleRangeRelations = computed(() => preparedRelations.value
231
+ .filter(preparedRelation => !preparedRelation.singular)
232
+ .map(preparedRelation => {
233
+ const accessibleViews = preparedRelation.views.filter(view => preparedRelation.access.value[view.name])
234
+ if(accessibleViews.length === 0) return null
235
+ return {
236
+ ...preparedRelation,
237
+ views: accessibleViews
238
+ }
239
+ })
240
+ .filter(x => x !== null)
241
+ )
211
242
 
212
243
  const visibleObjectRelations = computed(() => preparedRelations.value.filter(preparedRelation => {
213
244
  if(!preparedRelation.singular) return false
@@ -222,6 +253,43 @@
222
253
  }
223
254
  const accessControlRoles = computed(() => modelDefinition.value?.accessRoles ?? [])
224
255
 
256
+ const editRoute = computed(() => ({
257
+ name: 'auto-form:editor',
258
+ params: {
259
+ serviceName: service.value,
260
+ modelName: model.value,
261
+ identifiers: Object.values(identifiers.value)
262
+ }
263
+ }))
264
+
265
+ function actionRoute(action) {
266
+ const myType = service.value + '_' + model.value
267
+ const parameterName = action.objectParameter ??
268
+ Object.entries(action.definition.properties).find(([key, value]) => value.type === myType)?.[0] ??
269
+ Object.keys(action.definition.properties)?.[0]
270
+
271
+ if(parameterName) {
272
+ const parametersJson = JSON.stringify({
273
+ [parameterName]: object.value.to ?? object.value.id
274
+ })
275
+ return {
276
+ name: 'auto-form:actionParameters',
277
+ params: {
278
+ serviceName: action.service,
279
+ actionName: action.name,
280
+ parametersJson
281
+ }
282
+ }
283
+ }
284
+ return {
285
+ name: 'auto-form:action',
286
+ params: {
287
+ serviceName: action.service,
288
+ actionName: action.name
289
+ }
290
+ }
291
+ }
292
+
225
293
  </script>
226
294
 
227
295
  <style scoped>
@@ -0,0 +1,41 @@
1
+ <template>
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
+ </template>
13
+
14
+ <script setup>
15
+
16
+ import { computed, toRefs, defineProps, getCurrentInstance } from 'vue'
17
+
18
+ const props = defineProps({
19
+ data: {
20
+ type: Object,
21
+ required: true,
22
+ },
23
+ prefix: {
24
+ type: String,
25
+ default: ''
26
+ }
27
+ })
28
+ const { data } = toRefs(props)
29
+
30
+ import { getSchemaFromData, cleanData } from "../../logic/schema.js"
31
+
32
+ const appContext = getCurrentInstance().appContext
33
+
34
+ const schema = computed(() => getSchemaFromData(data.value, appContext))
35
+ const clearData = computed(() => cleanData(data.value))
36
+
37
+ </script>
38
+
39
+ <style scoped>
40
+
41
+ </style>
@@ -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>
@@ -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,
@@ -40,10 +40,10 @@ export default function editorData(options) {
40
40
 
41
41
  appContext = getCurrentInstance().appContext,
42
42
 
43
- toast = useToast(options.appContext),
44
- path = usePath(options.appContext),
45
- api = useApi(options.appContext),
46
- workingZone = inject('workingZone', options.appContext),
43
+ toast = useToast(options.appContext || getCurrentInstance().appContext),
44
+ path = usePath(options.appContext || getCurrentInstance().appContext),
45
+ api = useApi(options.appContext || getCurrentInstance().appContext),
46
+ workingZone = inject('workingZone', options.appContext || getCurrentInstance().appContext),
47
47
 
48
48
  } = options
49
49
 
@@ -255,7 +255,10 @@ export function parentObjectsFromIdentifiers(identifiers, modelDefinition) {
255
255
  const results = []
256
256
  for(const [key, value] of Object.entries(identifiers)) {
257
257
  if(key.endsWith('Type')) continue
258
+ const identifierDefinition = (modelDefinition.identifiers ?? []).find(i => i.name === key)
259
+ if(identifierDefinition && identifierDefinition.field === 'id') continue
258
260
  const propertyDefinition = modelDefinition.properties[key]
261
+ if(!propertyDefinition) continue
259
262
  const propertyType = propertyDefinition.type
260
263
  if(propertyType === 'any') {
261
264
  results.push({
@@ -0,0 +1,202 @@
1
+ import { sourceSymbol } from '@live-change/dao'
2
+ import { useApi } from '@live-change/vue3-ssr'
3
+ import { getCurrentInstance } from 'vue'
4
+
5
+
6
+ function mergeSchemas(s1, s2) {
7
+ return {
8
+ ...s1,
9
+ ...s2,
10
+ properties: {
11
+ ...s1.properties,
12
+ ...s2.properties
13
+ }
14
+ }
15
+ }
16
+
17
+ function addSchema(schemas, schema) {
18
+ for(let i = 0; i < schemas.length; i++) {
19
+ const s = schemas[i]
20
+ if(s.modelName === schema.modelName && s.serviceName === schema.serviceName) {
21
+ schemas[i] = mergeSchemas(s, schema)
22
+ return
23
+ }
24
+ }
25
+ schemas.push(schema)
26
+ }
27
+
28
+ export function schemaFromDefinition(definition, data, type, appContext = getCurrentInstance().appContext) {
29
+ if(!type) type = definition.type
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
+ description: `Type of ${keyWithoutType}`
43
+ }
44
+ properties[keyWithoutType] = {
45
+ type: 'string',
46
+ description: `Id of Object with type defined in ${key}`
47
+ }
48
+ }
49
+ }
50
+ return {
51
+ type: 'object',
52
+ properties,
53
+ description: definition.description
54
+ }
55
+ } else if(type === 'Array') {
56
+ const schema = {
57
+ type: 'array',
58
+ items: schemaFromDefinition(definition.items ?? definition.of, data?.[0], undefined, appContext),
59
+ description: definition.description
60
+ }
61
+ if(data) {
62
+ for(const item of data) {
63
+ extendSchema(schema.items, item, schema.items.modelName, schema.items.serviceName)
64
+ }
65
+ }
66
+ return schema
67
+ } else if(type === 'String') {
68
+ return {
69
+ type: 'string',
70
+ description: definition.description
71
+ }
72
+ } else if(type === 'Number') {
73
+ return {
74
+ type: 'number',
75
+ description: definition.description
76
+ }
77
+ } else if(type === 'Boolean') {
78
+ return {
79
+ type: 'boolean',
80
+ description: definition.description
81
+ }
82
+ } else if(type === 'Date') {
83
+ return {
84
+ type: 'string',
85
+ format: 'date-time',
86
+ description: definition.description
87
+ }
88
+ } else if(type) {
89
+ const api = useApi(appContext)
90
+ const [serviceName, modelName] = definition.type.split('_')
91
+ const serviceDefinition = api.getServiceDefinition(serviceName)
92
+ const modelDefinition = serviceDefinition?.models?.[modelName]
93
+ if(!data || typeof data === 'string') {
94
+ return {
95
+ type: 'string',
96
+ description: `Id of ${modelName} from ${serviceName} service.`
97
+ + (modelDefinition?.description ? `\n${modelDefinition.description}` : '')
98
+ }
99
+ } else {
100
+ const schema = schemaFromDefinition(modelDefinition, data, 'Object', appContext)
101
+ schema.serviceName = serviceName
102
+ schema.modelName = modelName
103
+ schema.description = [
104
+ `Object ${modelName} from ${serviceName} service.`,
105
+ definition.description,schema.description
106
+ ].filter(Boolean).join('\n')
107
+ return schema
108
+ }
109
+ } else {
110
+ console.log("UNHANDLED TYPE", definition)
111
+ }
112
+ }
113
+
114
+ function extendSchema(schema, data, viewName, serviceName, appContext) {
115
+ if(!data) return
116
+ if(Array.isArray(data)) {
117
+ if(!schema.items) {
118
+ schema.items = generateSchema(data, schema, 'items', appContext)
119
+ }
120
+ } else {
121
+ for(const property in data) {
122
+ const value = data[property]
123
+ if(!schema.properties[property]) { /// additional property
124
+ if(property === 'id' || property === 'to') {
125
+ schema.properties.id = {
126
+ type: 'string',
127
+ description: schema.modelName ? `Id of ${schema.modelName} from ${schema.serviceName} service.` : `Id.`
128
+ }
129
+ } else {
130
+ generateSchema(value, schema.properties, property, appContext)
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ function generateSchema(data, schemaObject, schemaProperty, appContext) {
138
+ const api = useApi(appContext)
139
+ if(!schemaObject[schemaProperty]) {
140
+ schemaObject[schemaProperty] = {
141
+ anyOf: []
142
+ }
143
+ }
144
+ let schemas = schemaObject[schemaProperty].anyOf
145
+ let viewDefinition = null
146
+ if(data?.[sourceSymbol]) {
147
+ const [serviceName, viewName] = data[sourceSymbol]
148
+ const serviceDefinition = api.getServiceDefinition(serviceName)
149
+ viewDefinition = serviceDefinition?.views?.[viewName]
150
+ }
151
+ if(viewDefinition) {
152
+ //console.log("VIEW DEFINITION", viewDefinition)
153
+ const schema = schemaFromDefinition(viewDefinition.returns, data, undefined, appContext)
154
+ extendSchema(schema, data, undefined, undefined, appContext)
155
+ addSchema(schemas, schema)
156
+ } else {
157
+
158
+ }
159
+ }
160
+
161
+ function cleanSchema(schema) {
162
+ if(schema.anyOf && schema.anyOf.length === 1) {
163
+ return cleanSchema(schema.anyOf[0])
164
+ } else if(schema.type === 'object') {
165
+ const cleanedProperties = Object.fromEntries(
166
+ Object.entries(schema.properties).map(([key, value]) => [key, cleanSchema(value)])
167
+ )
168
+ return {
169
+ ...schema,
170
+ properties: cleanedProperties
171
+ }
172
+ } else if(schema.type === 'array') {
173
+ return {
174
+ type: 'array',
175
+ items: cleanSchema(schema.items)
176
+ }
177
+ } else {
178
+ return schema
179
+ }
180
+ }
181
+
182
+ export function getSchemaFromData(data, appContext = getCurrentInstance().appContext) {
183
+ let schemaOutput = {}
184
+ generateSchema(data, schemaOutput, 'schema', appContext)
185
+ return cleanSchema(schemaOutput.schema)
186
+ }
187
+
188
+ export function cleanData(data) {
189
+ if(typeof data !== 'object') return data
190
+ if(Array.isArray(data)) {
191
+ return data.map(cleanData)
192
+ } else {
193
+ const cleanedProperties = Object.fromEntries(
194
+ Object.entries(data).map(([key, value]) => [key, cleanData(value)])
195
+ )
196
+ if(data.to && data.id) {
197
+ cleanedProperties.id = data.to
198
+ delete cleanedProperties.to
199
+ }
200
+ return cleanedProperties
201
+ }
202
+ }
@@ -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>
@@ -83,7 +83,7 @@
83
83
  //console.log("newIdentifiers", newIdentifiers)
84
84
  if(JSON.stringify(identifiers.value) !== JSON.stringify(newIdentifiers)) {
85
85
  router.push({
86
- name: 'auto-form:editor',
86
+ name: 'auto-form:view',
87
87
  params: {
88
88
  serviceName: serviceName.value,
89
89
  modelName: modelName.value,
@@ -93,6 +93,17 @@
93
93
  }
94
94
  }
95
95
 
96
+ function handleSaved(id) {
97
+ router.push({
98
+ name: 'auto-form:view',
99
+ params: {
100
+ serviceName: serviceName.value,
101
+ modelName: modelName.value,
102
+ identifiers: identifiers.value
103
+ }
104
+ })
105
+ }
106
+
96
107
  </script>
97
108
 
98
109
  <style scoped>
@@ -38,6 +38,12 @@ export function autoFormRoutes(config = {}) {
38
38
  props: true
39
39
  }),
40
40
 
41
+ route({
42
+ name: 'auto-form:actionParameters', path: prefix + '/action/:serviceName/:actionName/:parametersJson', meta: { },
43
+ component: () => import("./pages/Action.vue"),
44
+ props: true
45
+ }),
46
+
41
47
  ]
42
48
  }
43
49
 
package/index.js CHANGED
@@ -45,6 +45,12 @@ export { AutoObjectIdentification }
45
45
 
46
46
  export * from './front/src/router.js'
47
47
 
48
+ import DataWithSchema from './front/src/components/schema/DataWithSchema.vue'
49
+ export { DataWithSchema }
50
+ import SchemaFromDefinition from './front/src/components/schema/SchemaFromDefinition.vue'
51
+ export { SchemaFromDefinition }
52
+ export * from './front/src/logic/schema.js'
53
+
48
54
  import en from "./front/locales/en.json"
49
55
  const locales = { en }
50
56
  export { locales }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/frontend-auto-form",
3
- "version": "0.9.84",
3
+ "version": "0.9.86",
4
4
  "scripts": {
5
5
  "memDev": "node server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
6
6
  "localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
@@ -22,16 +22,16 @@
22
22
  "type": "module",
23
23
  "dependencies": {
24
24
  "@fortawesome/fontawesome-free": "^6.7.2",
25
- "@live-change/cli": "^0.9.84",
26
- "@live-change/dao": "^0.9.84",
27
- "@live-change/dao-vue3": "^0.9.84",
28
- "@live-change/dao-websocket": "^0.9.84",
29
- "@live-change/framework": "^0.9.84",
30
- "@live-change/image-frontend": "^0.9.84",
31
- "@live-change/image-service": "^0.9.84",
32
- "@live-change/session-service": "^0.9.84",
33
- "@live-change/vue3-components": "^0.9.84",
34
- "@live-change/vue3-ssr": "^0.9.84",
25
+ "@live-change/cli": "^0.9.86",
26
+ "@live-change/dao": "^0.9.86",
27
+ "@live-change/dao-vue3": "^0.9.86",
28
+ "@live-change/dao-websocket": "^0.9.86",
29
+ "@live-change/framework": "^0.9.86",
30
+ "@live-change/image-frontend": "^0.9.86",
31
+ "@live-change/image-service": "^0.9.86",
32
+ "@live-change/session-service": "^0.9.86",
33
+ "@live-change/vue3-components": "^0.9.86",
34
+ "@live-change/vue3-ssr": "^0.9.86",
35
35
  "@vueuse/core": "^12.3.0",
36
36
  "codeceptjs-assert": "^0.0.5",
37
37
  "compression": "^1.7.5",
@@ -52,7 +52,7 @@
52
52
  "vue3-scroll-border": "0.1.6"
53
53
  },
54
54
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "^0.9.84",
55
+ "@live-change/codeceptjs-helper": "^0.9.86",
56
56
  "codeceptjs": "^3.6.10",
57
57
  "generate-password": "1.7.1",
58
58
  "playwright": "1.49.1",
@@ -63,5 +63,5 @@
63
63
  "author": "Michał Łaszczewski <michal@laszczewski.pl>",
64
64
  "license": "ISC",
65
65
  "description": "",
66
- "gitHead": "5881a5406e3a39e673034f172266409ddb306f2e"
66
+ "gitHead": "7b0013ef101d9033402eeec45fd1be60e151aada"
67
67
  }