@live-change/frontend-auto-form 0.9.17 → 0.9.18
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/ModelEditor.vue +3 -4
- package/front/src/components/crud/ModelList.vue +21 -11
- package/front/src/components/crud/ModelSingle.vue +226 -0
- package/front/src/components/crud/ModelView.vue +53 -15
- package/front/src/logic/editorData.js +3 -1
- package/front/src/logic/relations.js +139 -8
- package/front/src/pages/Editor.vue +14 -1
- package/package.json +13 -13
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
|
|
4
|
-
<h4>identifiers as object</h4>
|
|
5
|
-
<pre>{{ identifiersObject }}</pre>
|
|
3
|
+
<!--
|
|
6
4
|
|
|
7
5
|
<h4>definition</h4>
|
|
8
|
-
<pre>{{ modelDefinition }}</pre
|
|
6
|
+
<pre>{{ modelDefinition }}</pre>
|
|
7
|
+
-->
|
|
9
8
|
|
|
10
9
|
<div class="">
|
|
11
10
|
Service <strong>{{ service }}</strong>
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<!-- <h4>definition</h4>
|
|
4
4
|
<pre>{{ modelDefinition }}</pre>-->
|
|
5
5
|
|
|
6
|
+
<pre>{{ modelsPathRangeConfig.view }}</pre>
|
|
7
|
+
<pre>{{ identifiers }}</pre>
|
|
8
|
+
|
|
6
9
|
<div class="surface-card w-full p-3 shadow-1 border-round mb-2">
|
|
7
10
|
<slot name="header">
|
|
8
11
|
<div class="">
|
|
@@ -15,8 +18,9 @@
|
|
|
15
18
|
</slot>
|
|
16
19
|
</div>
|
|
17
20
|
|
|
18
|
-
<div class="surface-card p-3 shadow-1 border-round" v-if="
|
|
19
|
-
<range-viewer
|
|
21
|
+
<div class="surface-card p-3 shadow-1 border-round" v-if="modelsPathRangeFunctions">
|
|
22
|
+
<range-viewer v-for="(modelsPathRangeFunction, index) in modelsPathRangeFunctions"
|
|
23
|
+
:key="JSON.stringify(modelsPathRangeConfig)+index"
|
|
20
24
|
:pathFunction="modelsPathRangeFunction"
|
|
21
25
|
:canLoadTop="false" canDropBottom
|
|
22
26
|
loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
|
|
@@ -83,7 +87,6 @@
|
|
|
83
87
|
</div>
|
|
84
88
|
</template>
|
|
85
89
|
</ConfirmPopup>
|
|
86
|
-
|
|
87
90
|
</div>
|
|
88
91
|
</template>
|
|
89
92
|
|
|
@@ -114,8 +117,12 @@
|
|
|
114
117
|
type: Object,
|
|
115
118
|
default: () => ({})
|
|
116
119
|
},
|
|
120
|
+
view: {
|
|
121
|
+
type: String,
|
|
122
|
+
default: undefined
|
|
123
|
+
}
|
|
117
124
|
})
|
|
118
|
-
const { service, model, identifiers } = toRefs(props)
|
|
125
|
+
const { service, model, identifiers, view } = toRefs(props)
|
|
119
126
|
|
|
120
127
|
import AutoObjectIdentification from './AutoObjectIdentification.vue'
|
|
121
128
|
|
|
@@ -141,18 +148,21 @@
|
|
|
141
148
|
service: service.value,
|
|
142
149
|
model: model.value,
|
|
143
150
|
definition: modelDefinition.value,
|
|
144
|
-
reverse: true
|
|
151
|
+
reverse: true,
|
|
152
|
+
view: modelDefinition.value?.crud?.[view.value ?? 'range']
|
|
145
153
|
}
|
|
146
154
|
})
|
|
147
|
-
const
|
|
155
|
+
const modelsPathRangeFunctions = computed(() => {
|
|
148
156
|
const config = modelsPathRangeConfig.value
|
|
149
|
-
const rangeView = config.
|
|
157
|
+
const rangeView = config.view
|
|
150
158
|
if(!path[config.service]) return null
|
|
151
159
|
if(!path[config.service][rangeView]) return null
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
let idents = identifiers.value
|
|
161
|
+
if(!Array.isArray(idents)) idents = [idents]
|
|
162
|
+
return idents.map(ident => (range) => path[config.service][rangeView]({
|
|
163
|
+
...ident,
|
|
154
164
|
...(config.reverse ? reverseRange(range) : range),
|
|
155
|
-
})
|
|
165
|
+
}))
|
|
156
166
|
})
|
|
157
167
|
|
|
158
168
|
function objectIdentifiers(object) {
|
|
@@ -198,7 +208,7 @@
|
|
|
198
208
|
params: {
|
|
199
209
|
serviceName: service.value,
|
|
200
210
|
modelName: model.value,
|
|
201
|
-
identifiers: Object.values(identifiers.value)
|
|
211
|
+
identifiers: Object.values(identifiers.value[0])
|
|
202
212
|
}
|
|
203
213
|
}))
|
|
204
214
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<!-- <h4>definition</h4>
|
|
4
|
+
<pre>{{ modelDefinition }}</pre>-->
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
<div class="surface-card w-full p-3 shadow-1 border-round mb-2">
|
|
8
|
+
<slot name="header">
|
|
9
|
+
<div class="">
|
|
10
|
+
Service <strong>{{ service }}</strong>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="text-2xl">
|
|
13
|
+
<strong>{{ model }}</strong>
|
|
14
|
+
</div>
|
|
15
|
+
</slot>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="surface-card p-3 shadow-1 border-round" v-if="modelsPaths">
|
|
19
|
+
<div v-for="({ value: object }, index) in (modelsData ?? [])">
|
|
20
|
+
<div v-if="!object" class="text-xl text-800 my-1 mx-3">
|
|
21
|
+
<strong>{{ model }}</strong> not found.
|
|
22
|
+
</div>
|
|
23
|
+
<div v-else
|
|
24
|
+
:key="JSON.stringify(modelsPaths[index])+index"
|
|
25
|
+
class="flex flex-row align-items-center justify-content-between my-3">
|
|
26
|
+
<router-link :to="viewRoute(object)" class="no-underline text-color">
|
|
27
|
+
<ObjectIdentification
|
|
28
|
+
:objectType="service + '_' + model"
|
|
29
|
+
:object="object.to ?? object.id"
|
|
30
|
+
:data="object"
|
|
31
|
+
class="text-xl"
|
|
32
|
+
/>
|
|
33
|
+
</router-link>
|
|
34
|
+
<div class="flex flex-row">
|
|
35
|
+
<router-link :to="viewRoute(object)" class="no-underline">
|
|
36
|
+
<Button icon="pi pi-eye" severity="primary" label="View" class="mr-2" />
|
|
37
|
+
</router-link>
|
|
38
|
+
|
|
39
|
+
<router-link :to="editRoute(object)" class="no-underline">
|
|
40
|
+
<Button icon="pi pi-pencil" severity="primary" label="Edit" class="mr-2" />
|
|
41
|
+
</router-link>
|
|
42
|
+
|
|
43
|
+
<Button v-if="modelDefinition.crud?.delete" @click="ev => deleteObject(ev, object)"
|
|
44
|
+
icon="pi pi-eraser" severity="primary" label="Delete" class="mr-2" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div v-else class="flex align-items-start p-4 bg-pink-100 border-round border-1 border-pink-300 mb-4">
|
|
50
|
+
<i class="pi pi-times-circle text-pink-900 text-2xl mr-3" />
|
|
51
|
+
<div class="mr-3">
|
|
52
|
+
<div class="text-pink-900 font-medium text-xl mb-3 line-height-1">Not authorized</div>
|
|
53
|
+
<p class="m-0 p-0 text-pink-700">
|
|
54
|
+
You do not have sufficient privileges to use this feature of this object.
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div v-if="modelDefinition.crud?.create && !modelsData.find(x => x?.value)"
|
|
60
|
+
class="mt-2 flex flex-row justify-content-end mr-2">
|
|
61
|
+
<router-link :to="createRoute" class="no-underline2">
|
|
62
|
+
<Button icon="pi pi-plus" :label="'Set '+model" />
|
|
63
|
+
</router-link>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<ConfirmPopup group="delete">
|
|
67
|
+
<template #message="slotProps">
|
|
68
|
+
<div class="flex flex-row align-items-center w-full gap-3 border-bottom-1 surface-border px-3 pt-1 pb-1">
|
|
69
|
+
<i class="pi pi-trash text-3xl text-primary-500"></i>
|
|
70
|
+
<p>
|
|
71
|
+
Do you want to delete {{ model[0].toLowerCase() + model.slice(1) }}
|
|
72
|
+
<ObjectIdentification
|
|
73
|
+
:objectType="service + '_' + model"
|
|
74
|
+
:object="slotProps.message.object.to ?? slotProps.message.object.id"
|
|
75
|
+
:data="slotProps.message.object"
|
|
76
|
+
/>
|
|
77
|
+
?
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
</ConfirmPopup>
|
|
82
|
+
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<script setup>
|
|
87
|
+
|
|
88
|
+
import ConfirmPopup from "primevue/confirmpopup"
|
|
89
|
+
import Button from "primevue/button"
|
|
90
|
+
|
|
91
|
+
import { useToast } from 'primevue/usetoast'
|
|
92
|
+
const toast = useToast()
|
|
93
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
94
|
+
const confirm = useConfirm()
|
|
95
|
+
|
|
96
|
+
import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
|
|
97
|
+
import { RangeViewer, injectComponent } from "@live-change/vue3-components"
|
|
98
|
+
|
|
99
|
+
const props = defineProps({
|
|
100
|
+
service: {
|
|
101
|
+
type: String,
|
|
102
|
+
required: true,
|
|
103
|
+
},
|
|
104
|
+
model: {
|
|
105
|
+
type: String,
|
|
106
|
+
required: true,
|
|
107
|
+
},
|
|
108
|
+
identifiers: {
|
|
109
|
+
type: Object,
|
|
110
|
+
default: () => ({})
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
const { service, model, identifiers } = toRefs(props)
|
|
114
|
+
|
|
115
|
+
import AutoObjectIdentification from './AutoObjectIdentification.vue'
|
|
116
|
+
|
|
117
|
+
const ObjectIdentification = computed(() =>
|
|
118
|
+
injectComponent({
|
|
119
|
+
name: 'ObjectIdentification',
|
|
120
|
+
type: service.value + '_' + model.value,
|
|
121
|
+
service: service.value,
|
|
122
|
+
model: model.value
|
|
123
|
+
}, AutoObjectIdentification)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
127
|
+
const api = useApi()
|
|
128
|
+
const path = usePath()
|
|
129
|
+
|
|
130
|
+
const modelDefinition = computed(() => {
|
|
131
|
+
return api.services?.[service.value]?.models?.[model.value]
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const modelsPathConfig = computed(() => {
|
|
135
|
+
return {
|
|
136
|
+
service: service.value,
|
|
137
|
+
model: model.value,
|
|
138
|
+
definition: modelDefinition.value,
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
const modelsPaths = computed(() => {
|
|
142
|
+
const config = modelsPathConfig.value
|
|
143
|
+
const readView = config.definition?.crud?.read
|
|
144
|
+
if(!path[config.service]) return null
|
|
145
|
+
if(!path[config.service][readView]) return null
|
|
146
|
+
let idents = identifiers.value
|
|
147
|
+
if(!Array.isArray(idents)) idents = [idents]
|
|
148
|
+
return idents.map(ident => path[config.service][readView]({
|
|
149
|
+
...ident
|
|
150
|
+
}))
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const modelsData = await Promise.all(
|
|
154
|
+
modelsPaths.value.map(path => live(path))
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
function objectIdentifiers(object) {
|
|
158
|
+
const identifiers = {}
|
|
159
|
+
for(const identifierDefinition of modelDefinition.value.identifiers) {
|
|
160
|
+
if(typeof identifierDefinition === 'string') {
|
|
161
|
+
identifiers[identifierDefinition] = object[identifierDefinition]
|
|
162
|
+
} else {
|
|
163
|
+
if(identifierDefinition.field === 'id') {
|
|
164
|
+
identifiers[identifierDefinition.name] = object?.to ?? object.id
|
|
165
|
+
} else {
|
|
166
|
+
identifiers[identifierDefinition.name] = object[identifierDefinition.field]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return identifiers
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function editRoute(object) {
|
|
174
|
+
return {
|
|
175
|
+
name: 'auto-form:editor',
|
|
176
|
+
params: {
|
|
177
|
+
serviceName: service.value,
|
|
178
|
+
modelName: model.value,
|
|
179
|
+
identifiers: Object.values(objectIdentifiers(object))
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function viewRoute(object) {
|
|
185
|
+
return {
|
|
186
|
+
name: 'auto-form:view',
|
|
187
|
+
params: {
|
|
188
|
+
serviceName: service.value,
|
|
189
|
+
modelName: model.value,
|
|
190
|
+
identifiers: Object.values(objectIdentifiers(object))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const createRoute = computed(() => ({
|
|
196
|
+
name: 'auto-form:editor',
|
|
197
|
+
params: {
|
|
198
|
+
serviceName: service.value,
|
|
199
|
+
modelName: model.value,
|
|
200
|
+
identifiers: Object.values(objectIdentifiers(identifiers.value[0]))
|
|
201
|
+
}
|
|
202
|
+
}))
|
|
203
|
+
|
|
204
|
+
function deleteObject(event, object) {
|
|
205
|
+
confirm.require({
|
|
206
|
+
group: 'delete',
|
|
207
|
+
target: event.currentTarget,
|
|
208
|
+
object,
|
|
209
|
+
acceptClass: "p-button-danger",
|
|
210
|
+
accept: async () => {
|
|
211
|
+
await api.actions[service.value][modelDefinition.value.crud.delete]({
|
|
212
|
+
...objectIdentifiers(object)
|
|
213
|
+
});
|
|
214
|
+
toast.add({ severity: "info", summary: model.value + " deleted", life: 1500 });
|
|
215
|
+
},
|
|
216
|
+
reject: () => {
|
|
217
|
+
toast.add({ severity: "error", summary: "Rejected", detail: "You have rejected", life: 3e3 });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
|
|
226
|
+
</style>
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
|
-
<div v-for="
|
|
34
|
-
<
|
|
35
|
-
:identifiers="
|
|
33
|
+
<div v-for="preparedRelation of visibleObjectRelations" class="mb-4">
|
|
34
|
+
<ModelSingle :service="preparedRelation.service" :model="preparedRelation.model"
|
|
35
|
+
:identifiers="preparedRelation.identifiers">
|
|
36
36
|
<template #header>
|
|
37
37
|
<div class="text-xl">
|
|
38
38
|
<ObjectIdentification
|
|
@@ -42,24 +42,42 @@
|
|
|
42
42
|
class="mr-2"
|
|
43
43
|
/>
|
|
44
44
|
<span class="mr-2 font-medium">{{ model }}'s</span>
|
|
45
|
-
<span class="font-bold">{{
|
|
45
|
+
<span class="font-bold">{{ preparedRelation.model }}</span>:
|
|
46
46
|
</div>
|
|
47
47
|
</template>
|
|
48
|
+
</ModelSingle>
|
|
49
|
+
</div>
|
|
48
50
|
|
|
51
|
+
<div v-for="preparedRelation of visibleRangeRelations" class="mb-4">
|
|
52
|
+
<ModelList :service="preparedRelation.service" :model="preparedRelation.model"
|
|
53
|
+
:identifiers="preparedRelation.identifiers" :view="preparedRelation.view">
|
|
54
|
+
<template #header>
|
|
55
|
+
<div class="text-xl">
|
|
56
|
+
<ObjectIdentification
|
|
57
|
+
:objectType="service + '_' + model"
|
|
58
|
+
:object="object.to ?? object.id"
|
|
59
|
+
:data="object"
|
|
60
|
+
class="mr-2"
|
|
61
|
+
/>
|
|
62
|
+
<span class="mr-2 font-medium">{{ model }}'s</span>
|
|
63
|
+
<span class="font-bold">{{ pluralize(preparedRelation.model) }}</span>:
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
49
66
|
</ModelList>
|
|
50
|
-
|
|
51
|
-
<pre>{{ relatedIdentifiers }}</pre>
|
|
52
|
-
|
|
53
|
-
<pre>{{ itemRelation }}</pre>
|
|
54
|
-
|
|
55
67
|
</div>
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
<div class="surface-card p-3 shadow-1 border-round">
|
|
70
|
+
|
|
71
|
+
<pre>{{ preparedRelations }}</pre>
|
|
58
72
|
|
|
59
73
|
<h4>Backward relations</h4>
|
|
60
|
-
<pre>{{
|
|
74
|
+
<pre>{{
|
|
75
|
+
backwardRelations.map(
|
|
76
|
+
({ from, relation, what }) => ({ from: from.serviceName + '_' + from.name, relation, what })
|
|
77
|
+
)
|
|
78
|
+
}}</pre>
|
|
61
79
|
|
|
62
|
-
</div
|
|
80
|
+
</div>
|
|
63
81
|
|
|
64
82
|
</div>
|
|
65
83
|
</template>
|
|
@@ -68,10 +86,11 @@
|
|
|
68
86
|
|
|
69
87
|
import AutoView from '../view/AutoView.vue'
|
|
70
88
|
import ModelList from './ModelList.vue'
|
|
89
|
+
import ModelSingle from './ModelSingle.vue'
|
|
71
90
|
|
|
72
91
|
import pluralize from 'pluralize'
|
|
73
92
|
import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
|
|
74
|
-
import { RangeViewer, injectComponent } from "@live-change/vue3-components"
|
|
93
|
+
import { RangeViewer, injectComponent, InjectComponent } from "@live-change/vue3-components"
|
|
75
94
|
|
|
76
95
|
const props = defineProps({
|
|
77
96
|
service: {
|
|
@@ -118,9 +137,9 @@
|
|
|
118
137
|
return api.services?.[service.value]?.models?.[model.value]
|
|
119
138
|
})
|
|
120
139
|
|
|
121
|
-
import { getForwardRelations, getBackwardRelations } from '../../logic/relations.js'
|
|
140
|
+
import { getForwardRelations, getBackwardRelations, anyRelationsTypes, prepareObjectRelations } from '../../logic/relations.js'
|
|
122
141
|
const forwardRelations = computed(() => getForwardRelations(modelDefinition.value, () => true, api))
|
|
123
|
-
const backwardRelations = computed(() => getBackwardRelations(modelDefinition.value,
|
|
142
|
+
const backwardRelations = computed(() => getBackwardRelations(modelDefinition.value, api))
|
|
124
143
|
|
|
125
144
|
const itemRelations = computed(
|
|
126
145
|
() => backwardRelations.value.filter(relation => relation.relation === 'itemOf')
|
|
@@ -143,6 +162,25 @@
|
|
|
143
162
|
[model.value[0].toLowerCase() + model.value.slice(1)]: object.value.to ?? object.value.id
|
|
144
163
|
}))
|
|
145
164
|
|
|
165
|
+
|
|
166
|
+
const preparedRelations = computed(() => {
|
|
167
|
+
const objectType = service.value + '_' + model.value
|
|
168
|
+
return prepareObjectRelations(objectType, object.value.to ?? object.value.id, api)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const visibleRangeRelations = computed(() => preparedRelations.value.filter(preparedRelation => {
|
|
172
|
+
if(preparedRelation.view && preparedRelation.access.value[preparedRelation.view]) return true
|
|
173
|
+
if(preparedRelation.access.value.range) return true
|
|
174
|
+
return false
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
const visibleObjectRelations = computed(() => preparedRelations.value.filter(preparedRelation => {
|
|
178
|
+
if(!preparedRelation.singular) return false
|
|
179
|
+
if(!preparedRelation.access.value.read) return false
|
|
180
|
+
return true
|
|
181
|
+
}))
|
|
182
|
+
|
|
183
|
+
|
|
146
184
|
</script>
|
|
147
185
|
|
|
148
186
|
<style scoped>
|
|
@@ -65,7 +65,7 @@ export default function editorData(options) {
|
|
|
65
65
|
draftIdParts.push(identifier)
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
const isNew = (idKey ? (!identifiers[idKey]) : (!draftIdParts.some(key =>
|
|
68
|
+
const isNew = (idKey ? (!identifiers[idKey]) : (!draftIdParts.some(key => identifiers[key])))
|
|
69
69
|
const draftId = (idKey ? identifiers[idKey]
|
|
70
70
|
: draftIdParts.map(key => JSON.stringify(identifiers[key])).join('_')) ?? 'new'
|
|
71
71
|
const draftIdentifiers = {
|
|
@@ -202,6 +202,7 @@ export default function editorData(options) {
|
|
|
202
202
|
saveDraft: synchronizedData.save,
|
|
203
203
|
savingDraft: synchronizedData.saving,
|
|
204
204
|
saved: savedData,
|
|
205
|
+
savedPath: savedDataPath,
|
|
205
206
|
draft: draftData,
|
|
206
207
|
sourceChanged /// needed for draft discard on concurrent save
|
|
207
208
|
}
|
|
@@ -239,6 +240,7 @@ export default function editorData(options) {
|
|
|
239
240
|
save: synchronizedData.save,
|
|
240
241
|
saving: synchronizedData.saving,
|
|
241
242
|
saved: savedData,
|
|
243
|
+
savedPath: savedDataPath,
|
|
242
244
|
reset,
|
|
243
245
|
model,
|
|
244
246
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { useApi } from '@live-change/vue3-ssr'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
function ensureArray(value) {
|
|
5
|
+
if(!Array.isArray(value)) return [value]
|
|
6
|
+
return value
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
function getWhat(relation) {
|
|
4
10
|
if(typeof relation === 'string') return relation
|
|
@@ -6,11 +12,26 @@ function getWhat(relation) {
|
|
|
6
12
|
}
|
|
7
13
|
function getWhats(relations) {
|
|
8
14
|
if(!Array.isArray(relations)) relations = [relations]
|
|
9
|
-
return relations.map(getWhat)
|
|
15
|
+
return relations.map(x => ensureArray(getWhat(x)))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const relationTypes = {
|
|
19
|
+
propertyOf: { singular: true, typed: true, owned: true },
|
|
20
|
+
boundTo: { singular: true, typed: true, owned: false },
|
|
21
|
+
itemOf: { singular: false, typed: true, owned: true },
|
|
22
|
+
relatedTo: { singular: false, typed: true, owned: false },
|
|
23
|
+
propertyOfAny: { singular: true, typed: false, owned: true },
|
|
24
|
+
boundToAny: { singular: true, typed: false, owned: false },
|
|
25
|
+
itemOfAny: { singular: false, typed: false, owned: true },
|
|
26
|
+
relatedToAny: { singular: false, typed: false, owned: false }
|
|
10
27
|
}
|
|
11
28
|
|
|
12
|
-
export const typedRelationsTypes =
|
|
13
|
-
|
|
29
|
+
export const typedRelationsTypes = Object.entries(relationTypes)
|
|
30
|
+
.filter(([key, value]) => value.typed)
|
|
31
|
+
.map(([key, value]) => key)
|
|
32
|
+
export const anyRelationsTypes = Object.entries(relationTypes)
|
|
33
|
+
.filter(([key, value]) => !value.typed)
|
|
34
|
+
.map(([key, value]) => key)
|
|
14
35
|
|
|
15
36
|
export function getModelByTypeName(model, api = useApi()) {
|
|
16
37
|
if(typeof model === 'string') {
|
|
@@ -27,20 +48,51 @@ export function getServiceByName(service, api = useApi()) {
|
|
|
27
48
|
return service
|
|
28
49
|
}
|
|
29
50
|
|
|
30
|
-
export function getForwardRelations(model,
|
|
51
|
+
export function getForwardRelations(model, api = useApi()) {
|
|
31
52
|
model = getModelByTypeName(model, api)
|
|
32
53
|
const results = []
|
|
33
54
|
for(const type of typedRelationsTypes) {
|
|
55
|
+
const relationType = relationTypes[type]
|
|
56
|
+
let relations = model[type]
|
|
57
|
+
if(!relations) continue
|
|
58
|
+
if(!Array.isArray(relations)) relations = [relations]
|
|
59
|
+
for(const relation of relations) {
|
|
60
|
+
const what = ensureArray(getWhat(relation))
|
|
61
|
+
const result = {
|
|
62
|
+
from: model,
|
|
63
|
+
relation: type,
|
|
64
|
+
what
|
|
65
|
+
}
|
|
66
|
+
results.push(result)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for(const type of anyRelationsTypes) {
|
|
70
|
+
const relationType = relationTypes[type]
|
|
34
71
|
let relations = model[type]
|
|
35
72
|
if(!relations) continue
|
|
36
73
|
if(!Array.isArray(relations)) relations = [relations]
|
|
37
74
|
for(const relation of relations) {
|
|
75
|
+
const fields = (Array.isArray(relation.to) ? relation.to : [relation.to ?? 'owner'])
|
|
76
|
+
const possibleTypes = fields.map(other => {
|
|
77
|
+
const name = other.name ? other.name : other
|
|
78
|
+
const typesConfig = relation[name + 'Types'] || []
|
|
79
|
+
const otherTypes = other.types || []
|
|
80
|
+
return Array.from(new Set(
|
|
81
|
+
typesConfig.concat(otherTypes)
|
|
82
|
+
))
|
|
83
|
+
})
|
|
84
|
+
const allTypes = Array.from(new Set(
|
|
85
|
+
(relation.parentsTypes || [])
|
|
86
|
+
.concat(possibleTypes.filter(x => !!x).flat()
|
|
87
|
+
)))
|
|
38
88
|
const result = {
|
|
39
89
|
from: model,
|
|
90
|
+
fields,
|
|
40
91
|
relation: type,
|
|
41
|
-
what:
|
|
92
|
+
what: allTypes,
|
|
93
|
+
any: allTypes.length === 0
|
|
42
94
|
}
|
|
43
|
-
|
|
95
|
+
results.push(result)
|
|
44
96
|
}
|
|
45
97
|
}
|
|
46
98
|
return results
|
|
@@ -49,10 +101,10 @@ export function getForwardRelations(model, filter = () => true, api = useApi())
|
|
|
49
101
|
export function getBackwardRelations(model, api = useApi()) {
|
|
50
102
|
model = getModelByTypeName(model, api)
|
|
51
103
|
const key = `${model.serviceName}_${model.name}`
|
|
52
|
-
console.log("KEY", key)
|
|
104
|
+
//console.log("KEY", key)
|
|
53
105
|
return Object.values(api.services).map(
|
|
54
106
|
service => Object.values(service.models).map(
|
|
55
|
-
model => getForwardRelations(model, ({ what }) => what
|
|
107
|
+
model => getForwardRelations(model, api).filter(({ what, any }) => what.includes(key))
|
|
56
108
|
).flat()
|
|
57
109
|
).flat()
|
|
58
110
|
}
|
|
@@ -74,3 +126,82 @@ export function getModelsWithRelation(relationName, api = useApi()) {
|
|
|
74
126
|
service => getServiceModelsWithRelation(service, relationName, api)
|
|
75
127
|
).flat()))
|
|
76
128
|
}
|
|
129
|
+
|
|
130
|
+
export function prepareObjectRelations(objectType, object, api = useApi()) {
|
|
131
|
+
const [service, model] = objectType.split('_')
|
|
132
|
+
const backwardRelations = getBackwardRelations(objectType, api)
|
|
133
|
+
const preparedBackwardRelations = backwardRelations.map(({ relation, from, what, fields }) => {
|
|
134
|
+
const relationConfig = from[relation]
|
|
135
|
+
|
|
136
|
+
const access = computed(() => {
|
|
137
|
+
const serviceMetadata = api.metadata.api.value.services.find(s => s.name === from.serviceName)
|
|
138
|
+
return Object.fromEntries(
|
|
139
|
+
Object.entries(from.crud).map(([key, value]) =>
|
|
140
|
+
[key, !!serviceMetadata.actions[value] || !!serviceMetadata.views[value]]
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if(anyRelationsTypes.includes(relation)) {
|
|
146
|
+
const identifiers = []
|
|
147
|
+
for(const field of fields) {
|
|
148
|
+
const possibleFieldTypes = (relationConfig[field+'Types'] ?? [])
|
|
149
|
+
.concat(relationConfig.parentsTypes ?? [])
|
|
150
|
+
if(possibleFieldTypes.length === 0 || possibleFieldTypes.includes(objectType)) {
|
|
151
|
+
identifiers.push({
|
|
152
|
+
[field+'Type']: objectType,
|
|
153
|
+
[field]: object
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
model: from.name,
|
|
159
|
+
service: from.serviceName,
|
|
160
|
+
fields,
|
|
161
|
+
relation,
|
|
162
|
+
what,
|
|
163
|
+
identifiers,
|
|
164
|
+
access,
|
|
165
|
+
singular: relationTypes[relation].singular && what.length < 2
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
const singular = relationTypes[relation].singular && what.length < 2
|
|
169
|
+
const typeView = from.crud?.['rangeBy_'+objectType]
|
|
170
|
+
? 'rangeBy_'+objectType
|
|
171
|
+
: undefined
|
|
172
|
+
const view = relationConfig?.view ?? (singular
|
|
173
|
+
? undefined
|
|
174
|
+
: typeView
|
|
175
|
+
) ?? undefined
|
|
176
|
+
const identifiers = []
|
|
177
|
+
if(typeView) {
|
|
178
|
+
identifiers.push({
|
|
179
|
+
[model[0].toLowerCase() + model.slice(1)]: object
|
|
180
|
+
})
|
|
181
|
+
} else {
|
|
182
|
+
for(let i = 0; i < what.length; i++) {
|
|
183
|
+
if(what[i] !== objectType) continue
|
|
184
|
+
const propertyName = relationConfig.propertyNames?.[i]
|
|
185
|
+
?? model[0].toLowerCase() + model.slice(1)
|
|
186
|
+
identifiers.push({
|
|
187
|
+
[propertyName]: object
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
console.log(objectType, "VIEW", view, from, singular)
|
|
192
|
+
return {
|
|
193
|
+
model: from.name,
|
|
194
|
+
service: from.serviceName,
|
|
195
|
+
fields,
|
|
196
|
+
relation,
|
|
197
|
+
what,
|
|
198
|
+
identifiers,
|
|
199
|
+
access,
|
|
200
|
+
view,
|
|
201
|
+
singular
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return preparedBackwardRelations
|
|
207
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-full lg:w-8 md:w-11">
|
|
3
|
+
|
|
4
|
+
<!-- <pre>{{ identifiers }}</pre>
|
|
5
|
+
<pre>{{ modelDefinition.identifiers }}</pre>
|
|
6
|
+
<pre>{{identifiersObject}}</pre>-->
|
|
7
|
+
|
|
3
8
|
<div class="surface-card p-3 shadow-1 border-round">
|
|
4
9
|
|
|
5
10
|
<ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft
|
|
@@ -26,7 +31,15 @@
|
|
|
26
31
|
},
|
|
27
32
|
identifiers: {
|
|
28
33
|
type: Array,
|
|
29
|
-
default: []
|
|
34
|
+
default: () => []
|
|
35
|
+
},
|
|
36
|
+
identifiersTypes: {
|
|
37
|
+
type: Array,
|
|
38
|
+
default: () => undefined
|
|
39
|
+
},
|
|
40
|
+
identifiersProperties: {
|
|
41
|
+
type: Array,
|
|
42
|
+
default: () => undefined
|
|
30
43
|
}
|
|
31
44
|
})
|
|
32
45
|
const { serviceName, modelName, identifiers } = toRefs(props)
|
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.18",
|
|
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.18",
|
|
26
|
+
"@live-change/dao": "^0.9.18",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.18",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.18",
|
|
29
|
+
"@live-change/framework": "^0.9.18",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.18",
|
|
31
|
+
"@live-change/image-service": "^0.9.18",
|
|
32
|
+
"@live-change/session-service": "^0.9.18",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.18",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.18",
|
|
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.
|
|
55
|
+
"@live-change/codeceptjs-helper": "^0.9.18",
|
|
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": "d7fd7ad0a0ea331caea9dc8385439b9c637525ed"
|
|
67
67
|
}
|