@live-change/frontend-auto-form 0.9.86 → 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.
- package/front/src/components/crud/AutoObjectIdentification.vue +1 -1
- package/front/src/components/crud/DefaultObjectIdentification.vue +72 -4
- package/front/src/components/crud/InjectedObjectIndentification.vue +3 -3
- package/front/src/components/crud/ModelEditor.vue +7 -7
- package/front/src/components/crud/ModelList.vue +55 -14
- package/front/src/components/crud/ModelSingle.vue +5 -5
- package/front/src/components/crud/ModelView.vue +23 -16
- package/front/src/components/view/AutoView.vue +19 -4
- package/front/src/components/view/DefaultFieldView.vue +1 -1
- package/front/src/logic/schema.js +8 -5
- package/front/src/logic/viewData.js +6 -6
- package/front/src/pages/{Editor.vue → Create.vue} +9 -38
- package/front/src/pages/Edit.vue +76 -0
- package/front/src/pages/Models.vue +1 -1
- package/front/src/pages/View.vue +5 -30
- package/front/src/router.js +9 -3
- package/package.json +14 -14
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<template v-if="
|
|
2
|
+
<template v-if="identificationParts.length > 0">
|
|
3
3
|
<span>
|
|
4
|
-
<i :class="[icon, 'mr-2']" style="font-size: 0.9em;"></i>
|
|
5
|
-
|
|
4
|
+
<i v-if="modelDefinition.icon" :class="[modelDefinition.icon, 'mr-2']" style="font-size: 0.9em;"></i>
|
|
5
|
+
<template v-for="(part, index) in identificationParts">
|
|
6
|
+
<i v-if="part.icon" :class="[part.icon, 'mr-2']" style="font-size: 0.9em;"></i>
|
|
7
|
+
<span :class="{ 'mr-2': index < identificationParts.length - 1 }">
|
|
8
|
+
<span v-if="part.isObject">
|
|
9
|
+
<InjectedObjectIndentification :type="part.type" :object="part.value" />
|
|
10
|
+
</span>
|
|
11
|
+
<span v-else-if="part.field">
|
|
12
|
+
{{ part.value }}
|
|
13
|
+
</span>
|
|
14
|
+
<span v-else>
|
|
15
|
+
{{ part.text ?? '' }}
|
|
16
|
+
</span>
|
|
17
|
+
</span>
|
|
18
|
+
</template>
|
|
19
|
+
</span>
|
|
6
20
|
</template>
|
|
7
21
|
<template v-else>
|
|
8
22
|
<span>
|
|
@@ -13,7 +27,7 @@
|
|
|
13
27
|
|
|
14
28
|
<script setup>
|
|
15
29
|
|
|
16
|
-
import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
|
|
30
|
+
import { ref, computed, onMounted, defineProps, defineEmits, toRefs, defineAsyncComponent } from 'vue'
|
|
17
31
|
|
|
18
32
|
const props = defineProps({
|
|
19
33
|
objectType: {
|
|
@@ -70,9 +84,63 @@
|
|
|
70
84
|
})
|
|
71
85
|
})
|
|
72
86
|
|
|
87
|
+
function getDefinitionProperty(path) {
|
|
88
|
+
const parts = path.split('.')
|
|
89
|
+
let current = modelDefinition.value
|
|
90
|
+
for(const part of parts) {
|
|
91
|
+
if(part === '*') {
|
|
92
|
+
return current?.items ?? current?.of
|
|
93
|
+
} else {
|
|
94
|
+
current = current?.properties?.[part]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return current
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const InjectedObjectIndentification = defineAsyncComponent(() => import('./InjectedObjectIndentification.vue'))
|
|
101
|
+
|
|
73
102
|
const loadedObjectData = await live(objectDataPath)
|
|
74
103
|
const objectData = computed(() => data.value || loadedObjectData.value)
|
|
75
104
|
|
|
105
|
+
function getObjectData(path) {
|
|
106
|
+
const parts = path.split('.')
|
|
107
|
+
let current = objectData.value
|
|
108
|
+
for(const part of parts) {
|
|
109
|
+
current = current[part]
|
|
110
|
+
}
|
|
111
|
+
return current
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const identificationParts = computed(() => {
|
|
115
|
+
const config = identificationConfig.value
|
|
116
|
+
if(!config) return []
|
|
117
|
+
const configArray = Array.isArray(config) ? config : [config]
|
|
118
|
+
return configArray.map(fieldConfig => {
|
|
119
|
+
if(typeof fieldConfig === 'string') {
|
|
120
|
+
const field = getDefinitionProperty(fieldConfig)
|
|
121
|
+
if(field) {
|
|
122
|
+
const isObject = field.type.indexOf('_') > 0
|
|
123
|
+
return { field: fieldConfig, isObject, type: field.type }
|
|
124
|
+
} else {
|
|
125
|
+
return { text: fieldConfig }
|
|
126
|
+
}
|
|
127
|
+
} else if(typeof fieldConfig === 'object') {
|
|
128
|
+
return fieldConfig
|
|
129
|
+
} else {
|
|
130
|
+
throw new Error('Unknown identification config: ' + JSON.stringify(fieldConfig))
|
|
131
|
+
}
|
|
132
|
+
}).map(part => {
|
|
133
|
+
if(part.type === 'any') {
|
|
134
|
+
part.type = getObjectData(part.field + 'Type')
|
|
135
|
+
part.isObject = (part.type && part.type.indexOf('_') > 0) || false
|
|
136
|
+
}
|
|
137
|
+
if(part.field) {
|
|
138
|
+
part.value = getObjectData(part.field)
|
|
139
|
+
}
|
|
140
|
+
return part
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
76
144
|
|
|
77
145
|
const icon = computed(() => {
|
|
78
146
|
if(modelDefinition.value?.iconProperty) return objectData[modelDefinition.value?.iconProperty]
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
})
|
|
46
46
|
const { type, object, data, link, inline } = toRefs(props)
|
|
47
47
|
|
|
48
|
-
import
|
|
48
|
+
import DefaultObjectIdentification from './DefaultObjectIdentification.vue'
|
|
49
49
|
|
|
50
50
|
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
51
51
|
const api = useApi()
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
type: type.value,
|
|
65
65
|
service: service.value,
|
|
66
66
|
model: model.value
|
|
67
|
-
},
|
|
67
|
+
}, DefaultObjectIdentification)
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
const modelDefinition = computed(() => {
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
params: {
|
|
116
116
|
serviceName: service.value,
|
|
117
117
|
modelName: model.value,
|
|
118
|
-
|
|
118
|
+
id: objectData.value?.to ?? objectData.value?.id
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
})
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
<!-- <pre>parentObjects = {{ parentObjects }}</pre>
|
|
23
23
|
<pre>scopesPath = {{ scopesPath }}</pre>
|
|
24
|
-
<pre>scopes = {{ scopes }}</pre>
|
|
24
|
+
<pre>scopes = {{ scopes }}</pre> -->
|
|
25
25
|
|
|
26
26
|
<div class="">
|
|
27
27
|
Service <strong>{{ service }}</strong>
|
|
@@ -171,19 +171,19 @@
|
|
|
171
171
|
emit('created', saveResult)
|
|
172
172
|
} else {
|
|
173
173
|
emit('saved', saveResult)
|
|
174
|
-
}
|
|
174
|
+
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
const viewRoute = computed(() => ({
|
|
178
|
-
name: 'auto-form:
|
|
177
|
+
const viewRoute = computed(() => editor.value?.saved && ({
|
|
178
|
+
name: 'auto-form:view',
|
|
179
179
|
params: {
|
|
180
180
|
serviceName: service.value,
|
|
181
181
|
modelName: model.value,
|
|
182
|
-
|
|
182
|
+
id: editor.value.saved?.value?.to ?? editor.value.saved?.value?.id
|
|
183
183
|
}
|
|
184
184
|
}))
|
|
185
185
|
|
|
186
|
-
const scopesPath = computed(() => path.scope.objectScopes({
|
|
186
|
+
const scopesPath = computed(() => parentObjects.value[0] && path.scope.objectScopes({
|
|
187
187
|
objectType: parentObjects.value[0].objectType, /// TODO: support multiple parent objects!
|
|
188
188
|
object: parentObjects.value[0].object
|
|
189
189
|
}))
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
:key="JSON.stringify(modelsPathRangeConfig)+index"
|
|
28
28
|
:pathFunction="modelsPathRangeFunction"
|
|
29
29
|
:canLoadTop="false" canDropBottom
|
|
30
|
-
loadBottomSensorSize="4000px" dropBottomSensorSize="
|
|
30
|
+
loadBottomSensorSize="4000px" dropBottomSensorSize="7000px">
|
|
31
31
|
<template #empty>
|
|
32
32
|
<div class="text-xl text-surface-800 dark:text-surface-50 my-1 mx-4">
|
|
33
33
|
No <strong>{{ pluralize(model[0].toLowerCase() + model.slice(1)) }}</strong> found.
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
class="text-xl"
|
|
44
44
|
/>
|
|
45
45
|
</router-link>
|
|
46
|
-
<div class="flex flex-row">
|
|
46
|
+
<div class="flex flex-row ml-4">
|
|
47
47
|
<router-link :to="viewRoute(object)" class="no-underline">
|
|
48
48
|
<Button icon="pi pi-eye" severity="primary" label="View" class="mr-2" />
|
|
49
49
|
</router-link>
|
|
@@ -70,9 +70,11 @@
|
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
|
-
<div v-if="modelDefinition.crud?.create" class="mt-2 flex flex-row justify-end mr-2">
|
|
74
|
-
<router-link
|
|
75
|
-
|
|
73
|
+
<div v-if="modelDefinition.crud?.create" class="mt-2 flex flex-row justify-end mr-2 gap-2">
|
|
74
|
+
<router-link v-for="createButton in createButtons" :key="JSON.stringify(createButton.identifiers)"
|
|
75
|
+
:to="createButton.route" class="no-underline2">
|
|
76
|
+
<Button v-if="createButton.as" icon="pi pi-plus" :label="'Create new '+model + ' as '+createButton.as" />
|
|
77
|
+
<Button v-else icon="pi pi-plus" :label="'Create new '+model" />
|
|
76
78
|
</router-link>
|
|
77
79
|
</div>
|
|
78
80
|
|
|
@@ -198,11 +200,11 @@
|
|
|
198
200
|
|
|
199
201
|
function editRoute(object) {
|
|
200
202
|
return {
|
|
201
|
-
name: 'auto-form:
|
|
203
|
+
name: 'auto-form:edit',
|
|
202
204
|
params: {
|
|
203
205
|
serviceName: service.value,
|
|
204
206
|
modelName: model.value,
|
|
205
|
-
|
|
207
|
+
id: object.to ?? object.id
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
}
|
|
@@ -213,23 +215,62 @@
|
|
|
213
215
|
params: {
|
|
214
216
|
serviceName: service.value,
|
|
215
217
|
modelName: model.value,
|
|
216
|
-
|
|
218
|
+
id: object.to ?? object.id
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
if(!identifiersObject) return null
|
|
224
|
-
const identifiers = Object.values(identifiersObject)
|
|
223
|
+
function createRoute(identifiersObject) {
|
|
224
|
+
const identifiersWithNames = Object.entries(identifiersObject).flat()
|
|
225
225
|
return {
|
|
226
|
-
name: 'auto-form:
|
|
226
|
+
name: 'auto-form:create',
|
|
227
227
|
params: {
|
|
228
228
|
serviceName: service.value,
|
|
229
229
|
modelName: model.value,
|
|
230
|
-
|
|
230
|
+
identifiersWithNames
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const createButtons = computed(() => {
|
|
236
|
+
const model = modelDefinition.value
|
|
237
|
+
const results = []
|
|
238
|
+
for(const view of viewsArray.value) {
|
|
239
|
+
const identifiersObject = view?.identifiers
|
|
240
|
+
if(!identifiersObject) continue
|
|
241
|
+
|
|
242
|
+
const identifierNames = Object.keys(identifiersObject)
|
|
243
|
+
const fieldParameters = identifierNames.filter(name => model.properties[name])
|
|
244
|
+
const otherParameters = identifierNames.filter(name => !model.properties[name])
|
|
245
|
+
|
|
246
|
+
if(otherParameters.length > 0) {
|
|
247
|
+
const viewName = model.crud[view.name]
|
|
248
|
+
const viewDefinition = serviceDefinition.value.views[viewName]
|
|
249
|
+
for(const otherParameter of otherParameters) {
|
|
250
|
+
const otherParameterType = viewDefinition.properties[otherParameter].type
|
|
251
|
+
const modelParameters = Object.entries(model.properties)
|
|
252
|
+
.filter(([name, property]) => otherParameterType === property.type)
|
|
253
|
+
for(const [name, property] of modelParameters) {
|
|
254
|
+
const newIdentifiersObject = {
|
|
255
|
+
[name]: identifiersObject[otherParameter]
|
|
256
|
+
}
|
|
257
|
+
results.push({
|
|
258
|
+
identifiers: newIdentifiersObject,
|
|
259
|
+
as: name,
|
|
260
|
+
route: createRoute(newIdentifiersObject)
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
results.push({
|
|
266
|
+
identifiers: identifiersObject,
|
|
267
|
+
route: createRoute(identifiersObject)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
}
|
|
273
|
+
return results
|
|
233
274
|
})
|
|
234
275
|
|
|
235
276
|
function deleteObject(event, object) {
|
|
@@ -189,11 +189,11 @@
|
|
|
189
189
|
|
|
190
190
|
function editRoute(object) {
|
|
191
191
|
return {
|
|
192
|
-
name: 'auto-form:
|
|
192
|
+
name: 'auto-form:edit',
|
|
193
193
|
params: {
|
|
194
194
|
serviceName: service.value,
|
|
195
195
|
modelName: model.value,
|
|
196
|
-
|
|
196
|
+
id: object.to ?? object.id
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
|
@@ -204,17 +204,17 @@
|
|
|
204
204
|
params: {
|
|
205
205
|
serviceName: service.value,
|
|
206
206
|
modelName: model.value,
|
|
207
|
-
|
|
207
|
+
id: object.to ?? object.id
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
const createRoute = computed(() => ({
|
|
213
|
-
name: 'auto-form:
|
|
213
|
+
name: 'auto-form:create',
|
|
214
214
|
params: {
|
|
215
215
|
serviceName: service.value,
|
|
216
216
|
modelName: model.value,
|
|
217
|
-
|
|
217
|
+
identifiersWithNames: Object.entries(objectIdentifiers(views.value[0].identifiers)).flat()
|
|
218
218
|
}
|
|
219
219
|
}))
|
|
220
220
|
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
<div class="flex flex-row flex-wrap justify-between align-items-top">
|
|
13
13
|
<div class="text-2xl mb-6">
|
|
14
|
-
<strong>{{ model }}</strong>
|
|
14
|
+
<strong class="mr-2">{{ model }}</strong>
|
|
15
15
|
<ObjectIdentification
|
|
16
16
|
:objectType="service + '_' + model"
|
|
17
17
|
:object="object.to ?? object.id"
|
|
18
18
|
:data="object"
|
|
19
|
-
class="
|
|
19
|
+
class=""
|
|
20
20
|
/>
|
|
21
21
|
</div>
|
|
22
22
|
<div class="flex flex-row flex-wrap justify-between align-items-top gap-2">
|
|
@@ -32,12 +32,18 @@
|
|
|
32
32
|
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
|
-
<div v-if="connectedActions"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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>
|
|
41
47
|
</div>
|
|
42
48
|
</div>
|
|
43
49
|
|
|
@@ -111,7 +117,7 @@
|
|
|
111
117
|
</div>
|
|
112
118
|
<pre>accessControlRoles = {{ accessControlRoles }}</pre> -->
|
|
113
119
|
|
|
114
|
-
<pre>
|
|
120
|
+
<pre>id = {{ id }}</pre>
|
|
115
121
|
|
|
116
122
|
<pre>definition = {{ modelDefinition }}</pre>
|
|
117
123
|
|
|
@@ -147,9 +153,9 @@
|
|
|
147
153
|
type: String,
|
|
148
154
|
required: true,
|
|
149
155
|
},
|
|
150
|
-
|
|
151
|
-
type:
|
|
152
|
-
|
|
156
|
+
id: {
|
|
157
|
+
type: String,
|
|
158
|
+
required: true,
|
|
153
159
|
},
|
|
154
160
|
attributes: {
|
|
155
161
|
type: Object,
|
|
@@ -160,7 +166,7 @@
|
|
|
160
166
|
default: ''
|
|
161
167
|
}
|
|
162
168
|
})
|
|
163
|
-
const { service, model,
|
|
169
|
+
const { service, model, id, attributes, i18n } = toRefs(props)
|
|
164
170
|
|
|
165
171
|
const emit = defineEmits(['saved', 'draftSaved', 'draftDiscarded', 'saveError', 'created' ])
|
|
166
172
|
|
|
@@ -191,6 +197,7 @@
|
|
|
191
197
|
...action
|
|
192
198
|
}
|
|
193
199
|
const actionDefinition = api.getServiceDefinition(config.service).actions[config.name]
|
|
200
|
+
if(!actionDefinition) throw new Error("Action " + config.service + "_" + config.name + " definition not found")
|
|
194
201
|
const label = actionDefinition.label ?? action.label ?? actionDefinition.name
|
|
195
202
|
return {
|
|
196
203
|
...config,
|
|
@@ -210,7 +217,7 @@
|
|
|
210
217
|
const viewDataPromise = viewData({
|
|
211
218
|
service: service.value,
|
|
212
219
|
model: model.value,
|
|
213
|
-
|
|
220
|
+
id: id.value,
|
|
214
221
|
path, api
|
|
215
222
|
})
|
|
216
223
|
|
|
@@ -254,11 +261,11 @@
|
|
|
254
261
|
const accessControlRoles = computed(() => modelDefinition.value?.accessRoles ?? [])
|
|
255
262
|
|
|
256
263
|
const editRoute = computed(() => ({
|
|
257
|
-
name: 'auto-form:
|
|
264
|
+
name: 'auto-form:edit',
|
|
258
265
|
params: {
|
|
259
266
|
serviceName: service.value,
|
|
260
267
|
modelName: model.value,
|
|
261
|
-
|
|
268
|
+
id: id.value
|
|
262
269
|
}
|
|
263
270
|
}))
|
|
264
271
|
|
|
@@ -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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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>
|
|
@@ -39,24 +39,25 @@ export function schemaFromDefinition(definition, data, type, appContext = getCur
|
|
|
39
39
|
properties[key] = {
|
|
40
40
|
type: 'string',
|
|
41
41
|
enum: prop.enum,
|
|
42
|
-
|
|
42
|
+
enumDescriptions: prop.enumDescriptions,
|
|
43
|
+
description: `Type of ${keyWithoutType}`,
|
|
43
44
|
}
|
|
44
45
|
properties[keyWithoutType] = {
|
|
45
46
|
type: 'string',
|
|
46
|
-
description: `Id of Object with type defined in ${key}
|
|
47
|
+
description: `Id of Object with type defined in ${key}`,
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
return {
|
|
51
52
|
type: 'object',
|
|
52
53
|
properties,
|
|
53
|
-
description: definition.description
|
|
54
|
+
description: definition.description,
|
|
54
55
|
}
|
|
55
56
|
} else if(type === 'Array') {
|
|
56
57
|
const schema = {
|
|
57
58
|
type: 'array',
|
|
58
59
|
items: schemaFromDefinition(definition.items ?? definition.of, data?.[0], undefined, appContext),
|
|
59
|
-
description: definition.description
|
|
60
|
+
description: definition.description,
|
|
60
61
|
}
|
|
61
62
|
if(data) {
|
|
62
63
|
for(const item of data) {
|
|
@@ -67,7 +68,9 @@ export function schemaFromDefinition(definition, data, type, appContext = getCur
|
|
|
67
68
|
} else if(type === 'String') {
|
|
68
69
|
return {
|
|
69
70
|
type: 'string',
|
|
70
|
-
description: definition.description
|
|
71
|
+
description: definition.description,
|
|
72
|
+
enum: definition.enum,
|
|
73
|
+
enumDescriptions: definition.enumDescriptions
|
|
71
74
|
}
|
|
72
75
|
} else if(type === 'Number') {
|
|
73
76
|
return {
|
|
@@ -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
|
-
|
|
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(!
|
|
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](
|
|
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
|
|
@@ -29,20 +29,12 @@
|
|
|
29
29
|
type: String,
|
|
30
30
|
required: true,
|
|
31
31
|
},
|
|
32
|
-
|
|
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,
|
|
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(
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
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,35 +62,17 @@
|
|
|
73
62
|
const router = useRouter()
|
|
74
63
|
|
|
75
64
|
function handleCreated(id) {
|
|
76
|
-
|
|
77
|
-
if(typeof identifier === 'object' && identifier.field === 'id') {
|
|
78
|
-
return id
|
|
79
|
-
}
|
|
80
|
-
return identifiers.value[i]
|
|
81
|
-
})
|
|
82
|
-
|
|
65
|
+
console.log("HANDLE CREATED", id)
|
|
83
66
|
//console.log("newIdentifiers", newIdentifiers)
|
|
84
|
-
if(JSON.stringify(identifiers.value) !== JSON.stringify(newIdentifiers)) {
|
|
85
|
-
router.push({
|
|
86
|
-
name: 'auto-form:view',
|
|
87
|
-
params: {
|
|
88
|
-
serviceName: serviceName.value,
|
|
89
|
-
modelName: modelName.value,
|
|
90
|
-
identifiers: newIdentifiers
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function handleSaved(id) {
|
|
97
67
|
router.push({
|
|
98
68
|
name: 'auto-form:view',
|
|
99
69
|
params: {
|
|
100
70
|
serviceName: serviceName.value,
|
|
101
71
|
modelName: modelName.value,
|
|
102
|
-
|
|
72
|
+
id
|
|
103
73
|
}
|
|
104
74
|
})
|
|
75
|
+
|
|
105
76
|
}
|
|
106
77
|
|
|
107
78
|
</script>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-8/12 md:w-11/12">
|
|
3
|
+
|
|
4
|
+
<!-- <pre>{{ identifiers }}</pre>
|
|
5
|
+
<pre>{{ modelDefinition.identifiers }}</pre> -->
|
|
6
|
+
<!-- <pre>{{identifiersObject}}</pre> -->
|
|
7
|
+
|
|
8
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
|
|
9
|
+
|
|
10
|
+
<ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft
|
|
11
|
+
@saved="handleSaved" />
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
|
|
19
|
+
import ModelEditor from "../components/crud/ModelEditor.vue"
|
|
20
|
+
|
|
21
|
+
import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
serviceName: {
|
|
25
|
+
type: String,
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
modelName: {
|
|
29
|
+
type: String,
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
id: {
|
|
33
|
+
type: String,
|
|
34
|
+
required: true,
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
const { serviceName, modelName, id } = toRefs(props)
|
|
38
|
+
|
|
39
|
+
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
40
|
+
const api = useApi()
|
|
41
|
+
const path = usePath()
|
|
42
|
+
|
|
43
|
+
const modelDefinition = computed(() => {
|
|
44
|
+
const service = api.services[serviceName.value]
|
|
45
|
+
if(!service) return null
|
|
46
|
+
const model = service.models[modelName.value]
|
|
47
|
+
if(!model) return null
|
|
48
|
+
return model
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const identifiersObject = computed(() => {
|
|
52
|
+
return {
|
|
53
|
+
[modelName.value[0].toLowerCase() + modelName.value.slice(1)]: id.value
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
import { useRouter } from 'vue-router'
|
|
58
|
+
const router = useRouter()
|
|
59
|
+
|
|
60
|
+
function handleSaved(result) {
|
|
61
|
+
console.log("HANDLE SAVED", result)
|
|
62
|
+
router.push({
|
|
63
|
+
name: 'auto-form:view',
|
|
64
|
+
params: {
|
|
65
|
+
serviceName: serviceName.value,
|
|
66
|
+
modelName: modelName.value,
|
|
67
|
+
id: id.value
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<style scoped>
|
|
75
|
+
|
|
76
|
+
</style>
|
package/front/src/pages/View.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-full lg:w-8/12 md:w-11/12">
|
|
3
3
|
|
|
4
|
-
<ModelView :service="serviceName" :model="modelName" :
|
|
4
|
+
<ModelView :service="serviceName" :model="modelName" :id="id" />
|
|
5
5
|
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
@@ -21,37 +21,12 @@
|
|
|
21
21
|
type: String,
|
|
22
22
|
required: true,
|
|
23
23
|
},
|
|
24
|
-
|
|
25
|
-
type:
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
const { serviceName, modelName, identifiers } = toRefs(props)
|
|
30
|
-
|
|
31
|
-
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
32
|
-
const api = useApi()
|
|
33
|
-
const path = usePath()
|
|
34
|
-
|
|
35
|
-
const modelDefinition = computed(() => {
|
|
36
|
-
const service = api.services[serviceName.value]
|
|
37
|
-
if(!service) return null
|
|
38
|
-
const model = service.models[modelName.value]
|
|
39
|
-
if(!model) return null
|
|
40
|
-
return model
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const identifiersObject = computed(() => {
|
|
44
|
-
const result = {}
|
|
45
|
-
for(const [i, identifier] of Object.entries(identifiers.value)) {
|
|
46
|
-
const identifierDefinition = modelDefinition.value.identifiers[i]
|
|
47
|
-
if(typeof identifierDefinition === 'string') {
|
|
48
|
-
result[identifierDefinition] = identifier
|
|
49
|
-
} else {
|
|
50
|
-
result[identifierDefinition.name] = identifier
|
|
51
|
-
}
|
|
24
|
+
id: {
|
|
25
|
+
type: String,
|
|
26
|
+
required: true,
|
|
52
27
|
}
|
|
53
|
-
return result
|
|
54
28
|
})
|
|
29
|
+
const { serviceName, modelName, id } = toRefs(props)
|
|
55
30
|
|
|
56
31
|
</script>
|
|
57
32
|
|
package/front/src/router.js
CHANGED
|
@@ -15,13 +15,19 @@ export function autoFormRoutes(config = {}) {
|
|
|
15
15
|
}),
|
|
16
16
|
|
|
17
17
|
route({
|
|
18
|
-
name: 'auto-form:
|
|
19
|
-
component: () => import("./pages/
|
|
18
|
+
name: 'auto-form:create', path: prefix + '/create/:serviceName/:modelName/:identifiersWithNames*', meta: { },
|
|
19
|
+
component: () => import("./pages/Create.vue"),
|
|
20
20
|
props: true
|
|
21
21
|
}),
|
|
22
22
|
|
|
23
23
|
route({
|
|
24
|
-
name: 'auto-form:
|
|
24
|
+
name: 'auto-form:edit', path: prefix + '/edit/:serviceName/:modelName/:id', meta: { },
|
|
25
|
+
component: () => import("./pages/Edit.vue"),
|
|
26
|
+
props: true
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
route({
|
|
30
|
+
name: 'auto-form:view', path: prefix + '/view/:serviceName/:modelName/:id', meta: { },
|
|
25
31
|
component: () => import("./pages/View.vue"),
|
|
26
32
|
props: true
|
|
27
33
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/frontend-auto-form",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.87",
|
|
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.
|
|
26
|
-
"@live-change/dao": "^0.9.
|
|
27
|
-
"@live-change/dao-vue3": "^0.9.
|
|
28
|
-
"@live-change/dao-websocket": "^0.9.
|
|
29
|
-
"@live-change/framework": "^0.9.
|
|
30
|
-
"@live-change/image-frontend": "^0.9.
|
|
31
|
-
"@live-change/image-service": "^0.9.
|
|
32
|
-
"@live-change/session-service": "^0.9.
|
|
33
|
-
"@live-change/vue3-components": "^0.9.
|
|
34
|
-
"@live-change/vue3-ssr": "^0.9.
|
|
25
|
+
"@live-change/cli": "^0.9.87",
|
|
26
|
+
"@live-change/dao": "^0.9.87",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.87",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.87",
|
|
29
|
+
"@live-change/framework": "^0.9.87",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.87",
|
|
31
|
+
"@live-change/image-service": "^0.9.87",
|
|
32
|
+
"@live-change/session-service": "^0.9.87",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.87",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.87",
|
|
35
35
|
"@vueuse/core": "^12.3.0",
|
|
36
36
|
"codeceptjs-assert": "^0.0.5",
|
|
37
37
|
"compression": "^1.7.5",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"v-shared-element": "3.1.1",
|
|
50
50
|
"vue-meta": "^3.0.0-alpha.9",
|
|
51
51
|
"vue-router": "^4.5.0",
|
|
52
|
-
"vue3-scroll-border": "0.1.
|
|
52
|
+
"vue3-scroll-border": "0.1.7"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@live-change/codeceptjs-helper": "^0.9.
|
|
55
|
+
"@live-change/codeceptjs-helper": "^0.9.87",
|
|
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": "
|
|
66
|
+
"gitHead": "7a7694ad2801b7ffa16f347aed441ca5f81ab5fd"
|
|
67
67
|
}
|