@live-change/frontend-auto-form 0.9.61 → 0.9.62
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/locales/en.json +7 -0
- package/front/src/components/crud/AutoObjectIdentification.vue +4 -3
- package/front/src/components/crud/ModelEditor.vue +11 -10
- package/front/src/components/crud/ModelList.vue +42 -25
- package/front/src/components/crud/ModelSingle.vue +28 -10
- package/front/src/components/crud/ModelView.vue +19 -10
- package/front/src/components/form/AutoField.vue +4 -1
- package/front/src/components/form/ObjectInput.vue +72 -6
- package/front/src/components/form/ObjectPicker.vue +240 -2
- package/front/src/components/form/inputConfigInjection.js +4 -4
- package/front/src/components/form/provideAutoInputConfiguration.js +2 -0
- package/front/src/logic/editorData.js +25 -1
- package/front/src/logic/relations.js +71 -27
- package/package.json +13 -13
package/front/locales/en.json
CHANGED
|
@@ -12,5 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"autoform": {
|
|
14
14
|
"addItem": "Add next item"
|
|
15
|
+
},
|
|
16
|
+
"objectPicker": {
|
|
17
|
+
"selectObjectType": "Select object type",
|
|
18
|
+
"currentType": "Select",
|
|
19
|
+
"fromService": "from",
|
|
20
|
+
"modelOption": "{model}",
|
|
21
|
+
"modelOptionWithService": "{service} - {model}"
|
|
15
22
|
}
|
|
16
23
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-if="typeof identificationConfig === 'string'">
|
|
3
|
-
<span
|
|
3
|
+
<span>
|
|
4
|
+
<i class="pi pi-box mr-2" style="font-size: 0.9em;"></i>{{ objectData[identificationConfig] }}
|
|
5
|
+
</span>
|
|
4
6
|
</template>
|
|
5
7
|
<template v-else>
|
|
6
|
-
<span>
|
|
7
|
-
<i class="pi pi-box mr-1"></i>
|
|
8
|
+
<span>
|
|
8
9
|
<strong>{{ objectType }}</strong>: {{ object ?? objectData.to ?? objectData.id }}
|
|
9
10
|
</span>
|
|
10
11
|
</template>
|
|
@@ -16,23 +16,24 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<form v-if="editor" @submit="handleSave" @reset="handleReset">
|
|
19
|
-
<div v-for="identifier in modelDefinition.identifiers">
|
|
20
|
-
<template v-if="identifier.slice(-4) !== 'Type'">
|
|
19
|
+
<div v-for="identifier in modelDefinition.identifiers">
|
|
20
|
+
<template v-if="(identifier.name ?? identifier).slice(-4) !== 'Type'">
|
|
21
21
|
<div v-if="identifiers[identifier]" class="flex flex-col mb-3">
|
|
22
|
-
<div class="min-w-[8rem] font-medium">{{ identifier }}</div>
|
|
22
|
+
<div class="min-w-[8rem] font-medium">{{ identifier.name ?? identifier }}</div>
|
|
23
23
|
<div class="">
|
|
24
24
|
<InjectedObjectIndentification
|
|
25
|
-
:type="identifiers[identifier+'Type']
|
|
26
|
-
|
|
25
|
+
:type="identifiers[(identifier.field ?? identifier)+'Type']
|
|
26
|
+
?? modelDefinition.properties[identifier.field ?? identifier].type"
|
|
27
|
+
:object="identifiers[identifier.field ?? identifier]"
|
|
27
28
|
/>
|
|
28
29
|
</div>
|
|
29
30
|
</div>
|
|
30
31
|
<div v-else class="flex flex-col mb-3">
|
|
31
32
|
<auto-field :key="identifier"
|
|
32
|
-
v-model="editor.value.value[identifier]"
|
|
33
|
-
:definition="modelDefinition.properties[identifier]"
|
|
34
|
-
:label="identifier"
|
|
35
|
-
:rootValue="props.rootValue" :propName="identifier"
|
|
33
|
+
v-model="editor.value.value[identifier.field ?? identifier]"
|
|
34
|
+
:definition="modelDefinition.properties[identifier.field ?? identifier]"
|
|
35
|
+
:label="identifier.name ?? identifier"
|
|
36
|
+
:rootValue="props.rootValue" :propName="identifier.field ?? identifier"
|
|
36
37
|
:i18n="i18n"
|
|
37
38
|
class="col-span-12" />
|
|
38
39
|
</div>
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
},
|
|
68
69
|
identifiers: {
|
|
69
70
|
type: Object,
|
|
70
|
-
default:
|
|
71
|
+
default: () => ({})
|
|
71
72
|
},
|
|
72
73
|
draft: {
|
|
73
74
|
type: Boolean,
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
</slot>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
+
<!-- <pre>modelsPathRangeConfig = {{ modelsPathRangeConfig }}</pre>
|
|
22
|
+
<pre>modelsPathRangeFunctions = {{ modelsPathRangeFunctions }}</pre> -->
|
|
23
|
+
|
|
21
24
|
<div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border" v-if="modelsPathRangeFunctions">
|
|
22
25
|
<range-viewer v-for="(modelsPathRangeFunction, index) in modelsPathRangeFunctions"
|
|
23
26
|
:key="JSON.stringify(modelsPathRangeConfig)+index"
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
</template>
|
|
56
59
|
</range-viewer>
|
|
57
60
|
</div>
|
|
61
|
+
|
|
58
62
|
<div v-else class="flex items-start p-6 bg-pink-100 rounded-border border border-pink-300 mb-6">
|
|
59
63
|
<i class="pi pi-times-circle text-pink-900 text-2xl mr-4" />
|
|
60
64
|
<div class="mr-4">
|
|
@@ -113,16 +117,18 @@
|
|
|
113
117
|
type: String,
|
|
114
118
|
required: true,
|
|
115
119
|
},
|
|
116
|
-
|
|
117
|
-
type:
|
|
118
|
-
default: () => (
|
|
119
|
-
},
|
|
120
|
-
view: {
|
|
121
|
-
type: String,
|
|
122
|
-
default: undefined
|
|
120
|
+
views: {
|
|
121
|
+
type: Array,
|
|
122
|
+
default: () => ([]) // it can be array of identifiers or single identifier
|
|
123
123
|
}
|
|
124
124
|
})
|
|
125
|
-
const { service, model,
|
|
125
|
+
const { service, model, views } = toRefs(props)
|
|
126
|
+
|
|
127
|
+
const viewsArray = computed(() => views.value?.length ? views.value : [{
|
|
128
|
+
name: 'range',
|
|
129
|
+
identifiers: {
|
|
130
|
+
}
|
|
131
|
+
}])
|
|
126
132
|
|
|
127
133
|
import AutoObjectIdentification from './AutoObjectIdentification.vue'
|
|
128
134
|
|
|
@@ -139,28 +145,34 @@
|
|
|
139
145
|
const api = useApi()
|
|
140
146
|
const path = usePath()
|
|
141
147
|
|
|
148
|
+
const serviceDefinition = computed(() => {
|
|
149
|
+
const serviceDefinition = api.metadata.api.value.services.find(s => s.name === service.value)
|
|
150
|
+
return serviceDefinition
|
|
151
|
+
})
|
|
152
|
+
|
|
142
153
|
const modelDefinition = computed(() => {
|
|
143
|
-
return
|
|
154
|
+
return serviceDefinition.value.models[model.value]
|
|
144
155
|
})
|
|
145
156
|
|
|
146
157
|
const modelsPathRangeConfig = computed(() => {
|
|
147
158
|
return {
|
|
148
159
|
service: service.value,
|
|
149
160
|
model: model.value,
|
|
150
|
-
definition: modelDefinition.value,
|
|
161
|
+
//definition: modelDefinition.value,
|
|
151
162
|
reverse: true,
|
|
152
|
-
|
|
163
|
+
views: viewsArray.value.map(view => ({
|
|
164
|
+
...view,
|
|
165
|
+
view: modelDefinition.value?.crud?.[view.name]
|
|
166
|
+
}))
|
|
153
167
|
}
|
|
154
168
|
})
|
|
155
169
|
const modelsPathRangeFunctions = computed(() => {
|
|
156
|
-
const config = modelsPathRangeConfig.value
|
|
157
|
-
const rangeView = config.view
|
|
170
|
+
const config = modelsPathRangeConfig.value
|
|
158
171
|
if(!path[config.service]) return null
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
...ident,
|
|
172
|
+
const views = config.views
|
|
173
|
+
const serviceViews = serviceDefinition.value.views
|
|
174
|
+
return views.map(view => (range) => serviceViews[view.view] && path[config.service][view.view]({
|
|
175
|
+
...view.identifiers,
|
|
164
176
|
...(config.reverse ? reverseRange(range) : range),
|
|
165
177
|
}))
|
|
166
178
|
})
|
|
@@ -203,14 +215,19 @@
|
|
|
203
215
|
}
|
|
204
216
|
}
|
|
205
217
|
|
|
206
|
-
const createRoute = computed(() =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
const createRoute = computed(() => {
|
|
219
|
+
const identifiersObject = viewsArray?.value[0]?.identifiers
|
|
220
|
+
if(!identifiersObject) return null
|
|
221
|
+
const identifiers = Object.values(identifiersObject)
|
|
222
|
+
return {
|
|
223
|
+
name: 'auto-form:editor',
|
|
224
|
+
params: {
|
|
225
|
+
serviceName: service.value,
|
|
226
|
+
modelName: model.value,
|
|
227
|
+
identifiers
|
|
228
|
+
}
|
|
212
229
|
}
|
|
213
|
-
})
|
|
230
|
+
})
|
|
214
231
|
|
|
215
232
|
function deleteObject(event, object) {
|
|
216
233
|
confirm.require({
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
</slot>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
+
<!-- <pre>{{ modelDefinition }}</pre> -->
|
|
19
|
+
|
|
18
20
|
<div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border" v-if="modelsPaths">
|
|
19
21
|
<div v-for="({ value: object }, index) in (modelsData ?? [])">
|
|
20
22
|
<div v-if="!object" class="text-xl text-surface-800 dark:text-surface-50 my-1 mx-4">
|
|
@@ -105,12 +107,12 @@
|
|
|
105
107
|
type: String,
|
|
106
108
|
required: true,
|
|
107
109
|
},
|
|
108
|
-
|
|
110
|
+
views: {
|
|
109
111
|
type: Object,
|
|
110
112
|
default: () => ({})
|
|
111
113
|
},
|
|
112
114
|
})
|
|
113
|
-
const { service, model,
|
|
115
|
+
const { service, model, views } = toRefs(props)
|
|
114
116
|
|
|
115
117
|
import AutoObjectIdentification from './AutoObjectIdentification.vue'
|
|
116
118
|
|
|
@@ -127,8 +129,13 @@
|
|
|
127
129
|
const api = useApi()
|
|
128
130
|
const path = usePath()
|
|
129
131
|
|
|
132
|
+
const serviceDefinition = computed(() => {
|
|
133
|
+
const serviceDefinition = api.metadata.api.value.services.find(s => s.name === service.value)
|
|
134
|
+
return serviceDefinition
|
|
135
|
+
})
|
|
136
|
+
|
|
130
137
|
const modelDefinition = computed(() => {
|
|
131
|
-
return
|
|
138
|
+
return serviceDefinition.value.models[model.value]
|
|
132
139
|
})
|
|
133
140
|
|
|
134
141
|
const modelsPathConfig = computed(() => {
|
|
@@ -138,15 +145,26 @@
|
|
|
138
145
|
definition: modelDefinition.value,
|
|
139
146
|
}
|
|
140
147
|
})
|
|
148
|
+
|
|
149
|
+
const modelsPathRangeConfig = computed(() => {
|
|
150
|
+
return {
|
|
151
|
+
service: service.value,
|
|
152
|
+
model: model.value,
|
|
153
|
+
//definition: modelDefinition.value,
|
|
154
|
+
reverse: true,
|
|
155
|
+
views: views.value.map(view => ({
|
|
156
|
+
...view,
|
|
157
|
+
view: modelDefinition.value?.crud?.[view.name]
|
|
158
|
+
}))
|
|
159
|
+
}
|
|
160
|
+
})
|
|
141
161
|
const modelsPaths = computed(() => {
|
|
142
|
-
const config =
|
|
143
|
-
const readView = config.definition?.crud?.read
|
|
162
|
+
const config = modelsPathRangeConfig.value
|
|
144
163
|
if(!path[config.service]) return null
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
...ident
|
|
164
|
+
const views = config.views
|
|
165
|
+
const serviceViews = serviceDefinition.value.views
|
|
166
|
+
return views.map(view => serviceViews[view.view] && path[config.service][view.view]({
|
|
167
|
+
...view.identifiers,
|
|
150
168
|
}))
|
|
151
169
|
})
|
|
152
170
|
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
<h4>object</h4>
|
|
11
11
|
<pre>{{ object }}</pre>-->
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
|
|
13
15
|
<div v-if="object">
|
|
14
16
|
<div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border mb-6">
|
|
15
17
|
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
|
|
37
39
|
<div v-for="preparedRelation of visibleObjectRelations" class="mb-6">
|
|
38
40
|
<ModelSingle :service="preparedRelation.service" :model="preparedRelation.model"
|
|
39
|
-
|
|
41
|
+
:views="preparedRelation.views">
|
|
40
42
|
<template #header>
|
|
41
43
|
<div class="text-xl">
|
|
42
44
|
<ObjectIdentification
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
|
|
55
57
|
<div v-for="preparedRelation of visibleRangeRelations" class="mb-6">
|
|
56
58
|
<ModelList :service="preparedRelation.service" :model="preparedRelation.model"
|
|
57
|
-
:
|
|
59
|
+
:views="preparedRelation.views">
|
|
58
60
|
<template #header>
|
|
59
61
|
<div class="text-xl">
|
|
60
62
|
<ObjectIdentification
|
|
@@ -91,7 +93,8 @@
|
|
|
91
93
|
|
|
92
94
|
<div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
|
|
93
95
|
|
|
94
|
-
<pre>{{
|
|
96
|
+
<pre>visibleRangeRelations = {{ visibleRangeRelations }}</pre>
|
|
97
|
+
<pre>preparedRelations = {{ preparedRelations }}</pre>
|
|
95
98
|
|
|
96
99
|
<div v-if="backwardRelations">
|
|
97
100
|
<h4>Backward relations</h4>
|
|
@@ -102,9 +105,11 @@
|
|
|
102
105
|
}}</pre>
|
|
103
106
|
</div>
|
|
104
107
|
|
|
108
|
+
|
|
109
|
+
<pre>accessControlRoles = {{ accessControlRoles }}</pre>
|
|
110
|
+
|
|
105
111
|
</div>
|
|
106
112
|
|
|
107
|
-
<pre>{{ accessControlRoles }}</pre>
|
|
108
113
|
|
|
109
114
|
</div>
|
|
110
115
|
</template>
|
|
@@ -166,7 +171,8 @@
|
|
|
166
171
|
return api.services?.[service.value]?.models?.[model.value]
|
|
167
172
|
})
|
|
168
173
|
|
|
169
|
-
import { getForwardRelations, getBackwardRelations, anyRelationsTypes, prepareObjectRelations }
|
|
174
|
+
import { getForwardRelations, getBackwardRelations, anyRelationsTypes, prepareObjectRelations }
|
|
175
|
+
from '../../logic/relations.js'
|
|
170
176
|
const forwardRelations = computed(() => getForwardRelations(modelDefinition.value, () => true, api))
|
|
171
177
|
const backwardRelations = computed(() => getBackwardRelations(modelDefinition.value, api))
|
|
172
178
|
|
|
@@ -192,11 +198,14 @@
|
|
|
192
198
|
return prepareObjectRelations(objectType.value, object.value.to ?? object.value.id, api)
|
|
193
199
|
})
|
|
194
200
|
|
|
195
|
-
const visibleRangeRelations = computed(() => preparedRelations.value.
|
|
196
|
-
|
|
197
|
-
if(
|
|
198
|
-
return
|
|
199
|
-
|
|
201
|
+
const visibleRangeRelations = computed(() => preparedRelations.value.map(preparedRelation => {
|
|
202
|
+
const accessibleViews = preparedRelation.views.filter(view => preparedRelation.access.value[view.name])
|
|
203
|
+
if(accessibleViews.length === 0) return null
|
|
204
|
+
return {
|
|
205
|
+
...preparedRelation,
|
|
206
|
+
views: accessibleViews
|
|
207
|
+
}
|
|
208
|
+
}).filter(x => x !== null))
|
|
200
209
|
|
|
201
210
|
const visibleObjectRelations = computed(() => preparedRelations.value.filter(preparedRelation => {
|
|
202
211
|
if(!preparedRelation.singular) return false
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
|
|
104
104
|
const definitionIf = computed(() => {
|
|
105
105
|
if(definition.value?.if) {
|
|
106
|
-
if(definition.value?.if.function) {
|
|
106
|
+
if(definition.value?.if.function) {
|
|
107
107
|
return eval(`(${definition.value.if.function})`)
|
|
108
108
|
} else {
|
|
109
109
|
throw new Error('Unknown if type' + JSON.stringify(definition.value.if))
|
|
@@ -114,6 +114,9 @@
|
|
|
114
114
|
|
|
115
115
|
const visible = computed(() => {
|
|
116
116
|
if(!definition.value) return false
|
|
117
|
+
if(definition.value.type === 'type' && props.propName.endsWith('Type') && !definition.value.showType) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
117
120
|
if(definitionIf.value) {
|
|
118
121
|
return definitionIf.value({
|
|
119
122
|
source: definition.value,
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
3
|
<div ref="selectElement" class="p-select p-component p-inputwrapper w-full" @click="toggleObjectPicker">
|
|
4
|
-
<span class="p-select-label p-placeholder" tabindex="0" role="combobox">
|
|
5
|
-
|
|
4
|
+
<span v-if="!model" class="p-select-label p-placeholder" tabindex="0" role="combobox">
|
|
5
|
+
<span v-if="isTypeSelectable">
|
|
6
|
+
Select object
|
|
7
|
+
</span>
|
|
8
|
+
<span v-else>
|
|
9
|
+
Select {{ objectModel }}
|
|
10
|
+
</span>
|
|
11
|
+
</span>
|
|
12
|
+
<span v-else class="p-select-label">
|
|
13
|
+
<ObjectIdentification
|
|
14
|
+
:objectType="objectType"
|
|
15
|
+
:object="model"
|
|
16
|
+
class=""
|
|
17
|
+
/>
|
|
6
18
|
</span>
|
|
7
19
|
<div class="p-select-dropdown" data-pc-section="dropdown">
|
|
8
20
|
<ChevronDownIcon />
|
|
9
21
|
</div>
|
|
10
22
|
</div>
|
|
11
23
|
|
|
24
|
+
<!-- <pre>objt = {{ objectType }}</pre> -->
|
|
25
|
+
|
|
12
26
|
<Popover ref="objectPickerPopover" :pt="{
|
|
13
27
|
root: {
|
|
14
28
|
class: 'object-picker-popover overflow-y-auto',
|
|
@@ -18,11 +32,13 @@
|
|
|
18
32
|
}
|
|
19
33
|
}
|
|
20
34
|
}">
|
|
21
|
-
<ObjectPicker
|
|
35
|
+
<ObjectPicker v-model="model" :definition="definition" :properties="properties"
|
|
36
|
+
:rootValue="rootValue" :propName="propName" :i18n="i18n" @selected="handleSelected" />
|
|
22
37
|
</Popover>
|
|
23
38
|
|
|
24
39
|
<!-- needed to autoload styles: -->
|
|
25
|
-
<Select class="hidden" />
|
|
40
|
+
<Select class="hidden" :options="[1,2,3]" />
|
|
41
|
+
|
|
26
42
|
</div>
|
|
27
43
|
</template>
|
|
28
44
|
|
|
@@ -31,8 +47,10 @@
|
|
|
31
47
|
import Select from 'primevue/select'
|
|
32
48
|
import Popover from 'primevue/popover'
|
|
33
49
|
import ObjectPicker from './ObjectPicker.vue'
|
|
50
|
+
import AutoObjectIdentification from '../crud/AutoObjectIdentification.vue'
|
|
51
|
+
import { injectComponent } from "@live-change/vue3-components"
|
|
34
52
|
|
|
35
|
-
import { defineProps, defineEmits, toRefs, ref, defineModel } from 'vue'
|
|
53
|
+
import { defineProps, defineEmits, toRefs, ref, defineModel, computed } from 'vue'
|
|
36
54
|
import { useElementSize } from '@vueuse/core'
|
|
37
55
|
|
|
38
56
|
const props = defineProps({
|
|
@@ -42,14 +60,58 @@
|
|
|
42
60
|
properties: {
|
|
43
61
|
type: Object,
|
|
44
62
|
default: () => ({})
|
|
63
|
+
},
|
|
64
|
+
rootValue: {
|
|
65
|
+
type: Object,
|
|
66
|
+
default: () => ({})
|
|
67
|
+
},
|
|
68
|
+
propName: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: ''
|
|
71
|
+
},
|
|
72
|
+
i18n: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: ''
|
|
45
75
|
}
|
|
46
76
|
})
|
|
47
77
|
|
|
78
|
+
const { definition, properties, rootValue, propName, i18n: i18nPrefix } = toRefs(props)
|
|
79
|
+
|
|
48
80
|
const model = defineModel({
|
|
49
81
|
required: true
|
|
50
82
|
})
|
|
83
|
+
|
|
84
|
+
const isTypeSelectable = computed(() => {
|
|
85
|
+
return definition.value.type === 'any'
|
|
86
|
+
})
|
|
51
87
|
|
|
52
|
-
const
|
|
88
|
+
const objectType = computed(() => {
|
|
89
|
+
if(definition.value.type !== 'any') {
|
|
90
|
+
return definition.value.type
|
|
91
|
+
}
|
|
92
|
+
const typePropertyName = props.propName + 'Type'
|
|
93
|
+
const typePropertyPath = typePropertyName.split('.')
|
|
94
|
+
return typePropertyPath.reduce((acc, prop) => acc?.[prop], rootValue.value)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const objectModel = computed(() => {
|
|
98
|
+
const type = objectType.value
|
|
99
|
+
if(!type) return null
|
|
100
|
+
const [service, model] = type.split('_')
|
|
101
|
+
return model
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const ObjectIdentification = computed(() => {
|
|
105
|
+
const type = objectType.value
|
|
106
|
+
if(!type) return AutoObjectIdentification
|
|
107
|
+
const [service, model] = type.split('_')
|
|
108
|
+
return injectComponent({
|
|
109
|
+
name: 'ObjectIdentification',
|
|
110
|
+
type,
|
|
111
|
+
service: service,
|
|
112
|
+
model: model
|
|
113
|
+
}, AutoObjectIdentification)
|
|
114
|
+
})
|
|
53
115
|
|
|
54
116
|
const objectPickerPopover = ref()
|
|
55
117
|
const selectElement = ref()
|
|
@@ -59,6 +121,10 @@
|
|
|
59
121
|
objectPickerPopover.value.toggle(event)
|
|
60
122
|
}
|
|
61
123
|
|
|
124
|
+
const handleSelected = (object) => {
|
|
125
|
+
objectPickerPopover.value.hide()
|
|
126
|
+
}
|
|
127
|
+
|
|
62
128
|
</script>
|
|
63
129
|
|
|
64
130
|
<style>
|
|
@@ -1,10 +1,248 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="!model && !selectedType && typeOptions.length < 8">
|
|
4
|
+
<div class="p-2">
|
|
5
|
+
<div class="text-sm text-surface-600 dark:text-surface-400 mb-2">
|
|
6
|
+
{{ t('objectPicker.selectObjectType') }}
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex flex-col">
|
|
9
|
+
<div v-for="type in typeOptions" :key="type"
|
|
10
|
+
class="p-2 hover:bg-surface-100 dark:hover:bg-surface-800 cursor-pointer"
|
|
11
|
+
@click="selectedType = type">
|
|
12
|
+
{{ typeLabel(type) }}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
4
16
|
</div>
|
|
17
|
+
<div v-else>
|
|
18
|
+
<div v-if="isTypeNeeded" class="bg-surface-100 dark:bg-surface-900 p-1 py-2 sticky top-0 z-10">
|
|
19
|
+
<div v-if="typeOptions.length > 1"
|
|
20
|
+
class="flex flex-col gap-2">
|
|
21
|
+
<!-- <label :for="`type-select-${uid}`">{{ t('objectPicker.selectObjectType') }}</label> -->
|
|
22
|
+
<Select v-model="selectedType" :options="typeOptions" :id="`type-select-${uid}`"
|
|
23
|
+
:placeholder="t('objectPicker.selectObjectType')"
|
|
24
|
+
:optionLabel="typeLabel" />
|
|
25
|
+
</div>
|
|
26
|
+
<div v-else>
|
|
27
|
+
<span>{{ t('objectPicker.currentType') }} {{ selectedTypeModel }}</span>
|
|
28
|
+
<span v-if="showServiceName">{{ t('objectPicker.fromService') }} {{ selectedTypeService }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div v-if="objectsPathRangeFunction">
|
|
33
|
+
<range-viewer :key="JSON.stringify(objectsPathRangeConfig)"
|
|
34
|
+
:pathFunction="objectsPathRangeFunction"
|
|
35
|
+
:canLoadTop="false" canDropBottom
|
|
36
|
+
loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
|
|
37
|
+
<template #empty>
|
|
38
|
+
<div class="text-xl text-surface-800 dark:text-surface-50 my-1 mx-4">
|
|
39
|
+
No <strong>
|
|
40
|
+
{{ pluralize(selectedTypeModel[0].toLowerCase() + selectedTypeModel.slice(1)) }}
|
|
41
|
+
</strong> found.
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
<template #default="{ item: object }">
|
|
45
|
+
<div @click="selectObject(object)"
|
|
46
|
+
class="flex flex-row items-center justify-between my-1 py-2 px-4
|
|
47
|
+
bg-surface-100 dark:bg-surface-900 hover:bg-surface-200 dark:hover:bg-surface-800">
|
|
48
|
+
<ObjectIdentification
|
|
49
|
+
:objectType="selectedTypeService + '_' + selectedTypeModel"
|
|
50
|
+
:object="object.to ?? object.id"
|
|
51
|
+
:data="object"
|
|
52
|
+
class=""
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
</range-viewer>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
<!-- <pre>isTypeNeeded = {{ isTypeNeeded }}</pre>
|
|
62
|
+
<pre>typePropertyName = {{ typePropertyName }}</pre>
|
|
63
|
+
<pre>typePropertyPath = {{ typePropertyPath }}</pre>
|
|
64
|
+
<pre>typeModel = {{ typeModel }}</pre> -->
|
|
65
|
+
<!-- <div>
|
|
66
|
+
<pre>objectsPathRangeConfig = {{ objectsPathRangeConfig }}</pre>
|
|
67
|
+
<pre>objectsPathRangeFunction = {{ objectsPathRangeFunction }}</pre>
|
|
68
|
+
<pre>selectedType = {{ selectedType }}</pre>
|
|
69
|
+
<pre>definition = {{ definition }}</pre>
|
|
70
|
+
<pre>properties = {{ properties }}</pre>
|
|
71
|
+
<pre>typeOptions = {{ typeOptions }}</pre>
|
|
72
|
+
<pre>model = {{ model }}</pre>
|
|
73
|
+
</div> -->
|
|
74
|
+
</div>
|
|
5
75
|
</template>
|
|
6
76
|
|
|
7
77
|
<script setup>
|
|
78
|
+
import Select from 'primevue/select'
|
|
79
|
+
import AutoObjectIdentification from '../crud/AutoObjectIdentification.vue'
|
|
80
|
+
import { RangeViewer, injectComponent } from "@live-change/vue3-components"
|
|
81
|
+
|
|
82
|
+
import { defineProps, defineEmits, toRefs, ref, defineModel, computed, useId } from 'vue'
|
|
83
|
+
import pluralize from 'pluralize'
|
|
84
|
+
|
|
85
|
+
const uid = useId()
|
|
86
|
+
|
|
87
|
+
const props = defineProps({
|
|
88
|
+
definition: {
|
|
89
|
+
type: Object
|
|
90
|
+
},
|
|
91
|
+
properties: {
|
|
92
|
+
type: Object,
|
|
93
|
+
default: () => ({})
|
|
94
|
+
},
|
|
95
|
+
rootValue: {
|
|
96
|
+
type: Object,
|
|
97
|
+
default: () => ({})
|
|
98
|
+
},
|
|
99
|
+
propName: {
|
|
100
|
+
type: String,
|
|
101
|
+
default: ''
|
|
102
|
+
},
|
|
103
|
+
i18n: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: ''
|
|
106
|
+
},
|
|
107
|
+
showServiceName: {
|
|
108
|
+
type: Boolean,
|
|
109
|
+
default: false
|
|
110
|
+
},
|
|
111
|
+
view: {
|
|
112
|
+
type: String,
|
|
113
|
+
default: 'range'
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const { definition, properties, rootValue, propName, i18n, view } = toRefs(props)
|
|
118
|
+
|
|
119
|
+
const model = defineModel({
|
|
120
|
+
required: true
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const emit = defineEmits(['selected'])
|
|
124
|
+
|
|
125
|
+
import { useI18n } from 'vue-i18n'
|
|
126
|
+
const { t: tI18n, te } = useI18n()
|
|
127
|
+
const t = (key, ...params) => tI18n(
|
|
128
|
+
te(i18n.value + propName.value + key)
|
|
129
|
+
? i18n.value + propName.value + key
|
|
130
|
+
: key, ...params
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const isTypeNeeded = computed(() => {
|
|
134
|
+
return definition.value.type === 'any'
|
|
135
|
+
})
|
|
136
|
+
const typePropertyName = computed(() => props.propName + 'Type')
|
|
137
|
+
const typePropertyPath = computed(() => typePropertyName.value.split('.'))
|
|
138
|
+
const typeModel = computed({
|
|
139
|
+
get() {
|
|
140
|
+
return typePropertyPath.value.reduce((acc, prop) => acc?.[prop], rootValue.value)
|
|
141
|
+
},
|
|
142
|
+
set(value) {
|
|
143
|
+
const path = typePropertyPath.value
|
|
144
|
+
let data = rootValue.value
|
|
145
|
+
for(let i = 0; i < path.length - 1; i++) {
|
|
146
|
+
const prop = path[i]
|
|
147
|
+
if(data[prop] === undefined || data[prop] === null) {
|
|
148
|
+
data[prop] = {}
|
|
149
|
+
}
|
|
150
|
+
data = data[prop]
|
|
151
|
+
}
|
|
152
|
+
data[path.at(-1)] = value
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
import { getAllTypesWithCrud } from '../../logic/relations'
|
|
157
|
+
|
|
158
|
+
const typeOptions = computed(() => {
|
|
159
|
+
if(definition.value.type === 'any') {
|
|
160
|
+
return definition.value.types ?? getAllTypesWithCrud('read')
|
|
161
|
+
}
|
|
162
|
+
return [definition.value.type]
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const typeOptionsServices = computed(() => {
|
|
166
|
+
return Array.from(new Set(typeOptions.value.map(option => option.split('_')[0])))
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const selectedType = ref(null)
|
|
170
|
+
|
|
171
|
+
console.log("Type options", typeOptions.value)
|
|
172
|
+
if(typeModel.value) {
|
|
173
|
+
selectedType.value = typeModel.value
|
|
174
|
+
} else if(typeOptions.value.length === 1) {
|
|
175
|
+
selectedType.value = typeOptions.value[0]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const selectedTypeParsed = computed(() => {
|
|
179
|
+
if(selectedType.value) return selectedType.value.split('_')
|
|
180
|
+
return null
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const selectedTypeService = computed(() => {
|
|
184
|
+
if(selectedTypeParsed.value) return selectedTypeParsed.value[0]
|
|
185
|
+
return null
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const selectedTypeModel = computed(() => {
|
|
189
|
+
if(selectedTypeParsed.value) return selectedTypeParsed.value[1]
|
|
190
|
+
return null
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const ObjectIdentification = computed(() => {
|
|
194
|
+
const [service, model] = selectedTypeParsed.value
|
|
195
|
+
return injectComponent({
|
|
196
|
+
name: 'ObjectIdentification',
|
|
197
|
+
type: service + '_' + model,
|
|
198
|
+
service: service,
|
|
199
|
+
model: model
|
|
200
|
+
}, AutoObjectIdentification)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
function typeLabel(option) {
|
|
204
|
+
const [service, model] = option.split('_')
|
|
205
|
+
if(typeOptionsServices.value.length === 1) {
|
|
206
|
+
return t('objectPicker.modelOption', { model })
|
|
207
|
+
}
|
|
208
|
+
return t('objectPicker.modelOptionWithService', { service, model })
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
import { useApi, usePath, live, reverseRange } from '@live-change/vue3-ssr'
|
|
212
|
+
const api = useApi()
|
|
213
|
+
const path = usePath()
|
|
214
|
+
|
|
215
|
+
const objectsPathRangeConfig = computed(() => {
|
|
216
|
+
if(!selectedTypeParsed.value) return null
|
|
217
|
+
const [service, model] = selectedTypeParsed.value
|
|
218
|
+
const serviceDefinition = api.metadata.api.value.services.find(s => s.name === service)
|
|
219
|
+
const modelDefinition = serviceDefinition.models[model]
|
|
220
|
+
return {
|
|
221
|
+
service,
|
|
222
|
+
model,
|
|
223
|
+
reverse: true,
|
|
224
|
+
view: modelDefinition?.crud?.[view.value ?? 'range']
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
const objectsPathRangeFunction = computed(() => {
|
|
228
|
+
const config = objectsPathRangeConfig.value
|
|
229
|
+
if(!config) return null
|
|
230
|
+
const rangeView = config.view
|
|
231
|
+
if(!path[config.service]) return null
|
|
232
|
+
if(!path[config.service][rangeView]) return null
|
|
233
|
+
return (range) => path[config.service][rangeView]({
|
|
234
|
+
//...ident,
|
|
235
|
+
...(config.reverse ? reverseRange(range) : range),
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
function selectObject(object) {
|
|
240
|
+
if(isTypeNeeded.value) {
|
|
241
|
+
typeModel.value = selectedType.value
|
|
242
|
+
}
|
|
243
|
+
model.value = object.to ?? object.id
|
|
244
|
+
emit('selected', object)
|
|
245
|
+
}
|
|
8
246
|
|
|
9
247
|
</script>
|
|
10
248
|
|
|
@@ -8,7 +8,7 @@ export function provideInputConfig(description, inputConfig) {
|
|
|
8
8
|
for(let key in description) {
|
|
9
9
|
for(let value of (description[key] instanceof Array ? description[key] : [description[key]])) {
|
|
10
10
|
const provideKey = `input:${key}=${value}`
|
|
11
|
-
console.log("PROVIDE
|
|
11
|
+
//console.log("PROVIDE INPUT CONFIG", provideKey)
|
|
12
12
|
provide(provideKey, {
|
|
13
13
|
inputConfig,
|
|
14
14
|
description
|
|
@@ -26,9 +26,9 @@ export function injectInputConfig(request, defaultInputConfig, factory) {
|
|
|
26
26
|
|
|
27
27
|
for(let key in request) {
|
|
28
28
|
const provideKey = `input:${key}=${request[key]}`
|
|
29
|
-
console.log("INJECT INPUT CONFIG PROVIDE KEY", provideKey)
|
|
29
|
+
//console.log("INJECT INPUT CONFIG PROVIDE KEY", provideKey)
|
|
30
30
|
const entry = inject(provideKey, null)
|
|
31
|
-
console.log("RESOLVED INPUT CONFIG", entry)
|
|
31
|
+
//console.log("RESOLVED INPUT CONFIG", entry)
|
|
32
32
|
if(!entry) continue
|
|
33
33
|
let isValid = true
|
|
34
34
|
for(let key in entry.description) {
|
|
@@ -37,7 +37,7 @@ export function injectInputConfig(request, defaultInputConfig, factory) {
|
|
|
37
37
|
if(!value.includes(entry.description[key])) isValid = false
|
|
38
38
|
} else if(value !== entry.description[key]) isValid = false
|
|
39
39
|
}
|
|
40
|
-
console.log("RESOLVED COMPONENT VALID", isValid, filter(entry))
|
|
40
|
+
//console.log("RESOLVED COMPONENT VALID", isValid, filter(entry))
|
|
41
41
|
if(isValid && filter(entry)) return entry.inputConfig
|
|
42
42
|
}
|
|
43
43
|
return factory ? defaultInputConfig() : defaultInputConfig
|
|
@@ -71,6 +71,8 @@ types.Boolean = inputs.switch = {
|
|
|
71
71
|
fieldComponent: defineAsyncComponent(() => import('./SwitchField.vue')),
|
|
72
72
|
}*/
|
|
73
73
|
|
|
74
|
+
types.any = inputs.object = inputConfig(() => import('./ObjectInput.vue'))
|
|
75
|
+
|
|
74
76
|
|
|
75
77
|
export function provideAutoInputConfiguration() {
|
|
76
78
|
for(let type in types) {
|
|
@@ -3,6 +3,27 @@ import { usePath, live, useApi } from '@live-change/vue3-ssr'
|
|
|
3
3
|
import { ref, computed, inject, watch } from 'vue'
|
|
4
4
|
import { synchronized, defaultData } from '@live-change/vue3-components'
|
|
5
5
|
|
|
6
|
+
function cyrb128(str) {
|
|
7
|
+
let h1 = 1779033703, h2 = 3144134277,
|
|
8
|
+
h3 = 1013904242, h4 = 2773480762;
|
|
9
|
+
for (let i = 0, k; i < str.length; i++) {
|
|
10
|
+
k = str.charCodeAt(i);
|
|
11
|
+
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
|
|
12
|
+
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
|
|
13
|
+
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
|
|
14
|
+
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
|
|
15
|
+
}
|
|
16
|
+
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
|
|
17
|
+
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
|
|
18
|
+
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
|
|
19
|
+
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
|
|
20
|
+
h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
|
|
21
|
+
const data = new Uint32Array([h4>>>0, h3>>>0, h2>>>0, h1>>>0])
|
|
22
|
+
// convert to base64
|
|
23
|
+
const data8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
24
|
+
return btoa(String.fromCharCode(...data8))
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
export default function editorData(options) {
|
|
7
28
|
if(!options) throw new Error('options must be provided')
|
|
8
29
|
|
|
@@ -66,8 +87,11 @@ export default function editorData(options) {
|
|
|
66
87
|
}
|
|
67
88
|
}
|
|
68
89
|
const isNew = (idKey ? (!identifiers[idKey]) : (!draftIdParts.every(key => identifiers[key])))
|
|
69
|
-
|
|
90
|
+
let draftId = (idKey ? identifiers[idKey]
|
|
70
91
|
: draftIdParts.map(key => JSON.stringify(identifiers[key])).join('_')) ?? 'new'
|
|
92
|
+
if(draftId.length > 16) {
|
|
93
|
+
draftId = cyrb128(draftId).slice(0, 16)
|
|
94
|
+
}
|
|
71
95
|
const draftIdentifiers = {
|
|
72
96
|
actionType: serviceName, action: crudMethods.read, targetType: modelName, target: draftId
|
|
73
97
|
}
|
|
@@ -15,6 +15,12 @@ function getWhats(relations) {
|
|
|
15
15
|
return relations.map(x => ensureArray(getWhat(x)))
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function getPropertyNames(relation) {
|
|
19
|
+
if(typeof relation === 'string') return relation[0].toLowerCase() + relation.slice(1)
|
|
20
|
+
if(relation.propertyNames) return relation.propertyNames
|
|
21
|
+
return ensureArray(relation.what).map(x => x[0].toLowerCase() + x.slice(1))
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
export const relationTypes = {
|
|
19
25
|
propertyOf: { singular: true, typed: true, owned: true },
|
|
20
26
|
boundTo: { singular: true, typed: true, owned: false },
|
|
@@ -58,10 +64,12 @@ export function getForwardRelations(model, api = useApi()) {
|
|
|
58
64
|
if(!Array.isArray(relations)) relations = [relations]
|
|
59
65
|
for(const relation of relations) {
|
|
60
66
|
const what = ensureArray(getWhat(relation))
|
|
67
|
+
const fields = ensureArray(getPropertyNames(relation))
|
|
61
68
|
const result = {
|
|
62
69
|
from: model,
|
|
63
70
|
relation: type,
|
|
64
|
-
what
|
|
71
|
+
what,
|
|
72
|
+
fields
|
|
65
73
|
}
|
|
66
74
|
results.push(result)
|
|
67
75
|
}
|
|
@@ -143,61 +151,83 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
|
|
|
143
151
|
})
|
|
144
152
|
|
|
145
153
|
if(anyRelationsTypes.includes(relation)) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
[
|
|
154
|
-
|
|
154
|
+
const views = []
|
|
155
|
+
const singular = relationTypes[relation].singular && fields.length < 2
|
|
156
|
+
|
|
157
|
+
if(singular) {
|
|
158
|
+
views.push({
|
|
159
|
+
name: 'read',
|
|
160
|
+
identifiers: {
|
|
161
|
+
[fields[0]+'Type']: objectType,
|
|
162
|
+
[fields[0]]: object
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
} else {
|
|
166
|
+
for(const field of fields) {
|
|
167
|
+
const possibleFieldTypes = (relationConfig[field+'Types'] ?? [])
|
|
168
|
+
.concat(relationConfig.parentsTypes ?? [])
|
|
169
|
+
if(possibleFieldTypes.length === 0 || possibleFieldTypes.includes(objectType)) {
|
|
170
|
+
const name = 'rangeBy' + field[0].toUpperCase() + field.slice(1)
|
|
171
|
+
if(from.crud?.[name]) views.push({
|
|
172
|
+
name,
|
|
173
|
+
identifiers: {
|
|
174
|
+
[field+'Type']: objectType,
|
|
175
|
+
[field]: object
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
155
179
|
}
|
|
156
180
|
}
|
|
181
|
+
/* console.error("relation", relation, 'from', from, "type", relationTypes[relation],
|
|
182
|
+
"what", what, "fields", fields) */
|
|
157
183
|
return {
|
|
158
184
|
model: from.name,
|
|
159
185
|
service: from.serviceName,
|
|
160
186
|
fields,
|
|
161
187
|
relation,
|
|
162
188
|
what,
|
|
163
|
-
|
|
189
|
+
views,
|
|
164
190
|
access,
|
|
165
|
-
singular
|
|
191
|
+
singular
|
|
166
192
|
}
|
|
167
193
|
} else {
|
|
194
|
+
const views = []
|
|
168
195
|
const singular = relationTypes[relation].singular && what.length < 2
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
? undefined
|
|
174
|
-
: typeView
|
|
175
|
-
) ?? undefined
|
|
176
|
-
const identifiers = []
|
|
196
|
+
const name = 'rangeBy_' + objectType
|
|
197
|
+
const typeView = from.crud?.[name]
|
|
198
|
+
? name
|
|
199
|
+
: undefined
|
|
177
200
|
if(typeView) {
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
views.push({
|
|
202
|
+
name: typeView,
|
|
203
|
+
identifiers: {
|
|
204
|
+
[model[0].toLowerCase() + model.slice(1)]: object
|
|
205
|
+
}
|
|
180
206
|
})
|
|
181
207
|
} else {
|
|
182
208
|
for(let i = 0; i < what.length; i++) {
|
|
183
209
|
if(what[i] !== objectType) continue
|
|
184
210
|
const propertyName = relationConfig.propertyNames?.[i]
|
|
185
211
|
?? model[0].toLowerCase() + model.slice(1)
|
|
186
|
-
|
|
187
|
-
|
|
212
|
+
const name = 'rangeBy' + propertyName[0].toUpperCase() + propertyName.slice(1)
|
|
213
|
+
if(!from.crud?.[name]) continue
|
|
214
|
+
views.push({
|
|
215
|
+
name,
|
|
216
|
+
identifiers: {
|
|
217
|
+
[propertyName]: object
|
|
218
|
+
}
|
|
188
219
|
})
|
|
189
220
|
}
|
|
190
221
|
}
|
|
191
|
-
console.log(objectType, "
|
|
222
|
+
console.log(objectType, "VIEWS", views, "FROM", from, "SINGULAR", singular)
|
|
192
223
|
return {
|
|
193
224
|
model: from.name,
|
|
194
225
|
service: from.serviceName,
|
|
195
226
|
fields,
|
|
196
227
|
relation,
|
|
197
228
|
what,
|
|
198
|
-
identifiers,
|
|
199
229
|
access,
|
|
200
|
-
|
|
230
|
+
views,
|
|
201
231
|
singular
|
|
202
232
|
}
|
|
203
233
|
}
|
|
@@ -205,3 +235,17 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
|
|
|
205
235
|
|
|
206
236
|
return preparedBackwardRelations
|
|
207
237
|
}
|
|
238
|
+
|
|
239
|
+
export function getAllPossibleTypes(api = useApi(), filter = () => true,) {
|
|
240
|
+
return Object.entries(api.services).map(
|
|
241
|
+
([serviceName, service]) => Object.values(service.models).filter(o => filter(o, service, serviceName)).map(
|
|
242
|
+
model => `${serviceName}_${model.name}`
|
|
243
|
+
).flat()
|
|
244
|
+
).flat()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getAllTypesWithCrud(crud, api = useApi()) {
|
|
248
|
+
return getAllPossibleTypes(api, (model, service, serviceName) => {
|
|
249
|
+
if(model.crud?.[crud]) return true
|
|
250
|
+
})
|
|
251
|
+
}
|
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.62",
|
|
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.62",
|
|
26
|
+
"@live-change/dao": "^0.9.62",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.62",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.62",
|
|
29
|
+
"@live-change/framework": "^0.9.62",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.62",
|
|
31
|
+
"@live-change/image-service": "^0.9.62",
|
|
32
|
+
"@live-change/session-service": "^0.9.62",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.62",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.62",
|
|
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.62",
|
|
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": "b1b605b7f1fa4fc3de4720afbb401e2cfff080cf"
|
|
67
67
|
}
|