@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.
@@ -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><i class="pi pi-box mr-1"></i>{{ objectData[identificationConfig] }}</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'] ?? modelDefinition.properties[identifier].type"
26
- :object="identifiers[identifier]"
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
- identifiers: {
117
- type: Object,
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, identifiers, view } = toRefs(props)
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 api.services?.[service.value]?.models?.[model.value]
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
- view: modelDefinition.value?.crud?.[view.value ?? 'range']
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
- if(!path[config.service][rangeView]) return null
160
- let idents = identifiers.value
161
- if(!Array.isArray(idents)) idents = [idents]
162
- return idents.map(ident => (range) => path[config.service][rangeView]({
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
- name: 'auto-form:editor',
208
- params: {
209
- serviceName: service.value,
210
- modelName: model.value,
211
- identifiers: Object.values(identifiers.value[0])
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
- identifiers: {
110
+ views: {
109
111
  type: Object,
110
112
  default: () => ({})
111
113
  },
112
114
  })
113
- const { service, model, identifiers } = toRefs(props)
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 api.services?.[service.value]?.models?.[model.value]
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 = modelsPathConfig.value
143
- const readView = config.definition?.crud?.read
162
+ const config = modelsPathRangeConfig.value
144
163
  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
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
- :identifiers="preparedRelation.identifiers">
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
- :identifiers="preparedRelation.identifiers" :view="preparedRelation.view">
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>{{ preparedRelations }}</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 } from '../../logic/relations.js'
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.filter(preparedRelation => {
196
- if(preparedRelation.view && preparedRelation.access.value[preparedRelation.view]) return true
197
- if(preparedRelation.access.value.range) return true
198
- return false
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
- Select object
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 { value, definition, modelValue } = toRefs(props)
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
- <div>
3
- Object picker!
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 COMPONENT", provideKey)
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
- const draftId = (idKey ? identifiers[idKey]
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 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
- })
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
- identifiers,
189
+ views,
164
190
  access,
165
- singular: relationTypes[relation].singular && what.length < 2
191
+ singular
166
192
  }
167
193
  } else {
194
+ const views = []
168
195
  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 = []
196
+ const name = 'rangeBy_' + objectType
197
+ const typeView = from.crud?.[name]
198
+ ? name
199
+ : undefined
177
200
  if(typeView) {
178
- identifiers.push({
179
- [model[0].toLowerCase() + model.slice(1)]: object
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
- identifiers.push({
187
- [propertyName]: object
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, "VIEW", view, from, singular)
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
- view,
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.61",
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.61",
26
- "@live-change/dao": "^0.9.61",
27
- "@live-change/dao-vue3": "^0.9.61",
28
- "@live-change/dao-websocket": "^0.9.61",
29
- "@live-change/framework": "^0.9.61",
30
- "@live-change/image-frontend": "^0.9.61",
31
- "@live-change/image-service": "^0.9.61",
32
- "@live-change/session-service": "^0.9.61",
33
- "@live-change/vue3-components": "^0.9.61",
34
- "@live-change/vue3-ssr": "^0.9.61",
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.61",
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": "f750f29f912b9a76b5ffa2bdf5c46954cc7e7a67"
66
+ "gitHead": "b1b605b7f1fa4fc3de4720afbb401e2cfff080cf"
67
67
  }