@live-change/frontend-auto-form 0.9.198 → 0.9.200

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/README.md ADDED
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: @live-change/frontend-auto-form
3
+ ---
4
+
5
+ ## @live-change/frontend-auto-form
6
+
7
+ `@live-change/frontend-auto-form` to moduł do **automatycznego generowania formularzy i CRUD-ów** na podstawie:
8
+
9
+ - definicji modeli i akcji po stronie serwera (Live Change)
10
+ - metadanych wygenerowanych przez `@live-change/framework` i dostępnych przez `@live-change/vue3-ssr`
11
+
12
+ Zapewnia:
13
+
14
+ - komponenty **pól formularza** (`AutoField`, `AutoInput`, `AutoEditor`, `ModelEditor`, `ModelView`, `ModelSingle`, `ActionForm`, itp.)
15
+ - provider-y konfiguracji (`provideAutoViewComponents`, `provideAutoInputConfiguration`, `provideMetadataBasedAutoInputConfiguration`)
16
+ - lokalizacje (`locales`) używane w aplikacjach takich jak `family-tree` i `speed-dating`
17
+
18
+ ### Podstawowa integracja
19
+
20
+ W `App.vue` typowej aplikacji włączasz auto-form globalnie:
21
+
22
+ ```javascript
23
+ import {
24
+ provideAutoViewComponents,
25
+ provideAutoInputConfiguration,
26
+ provideMetadataBasedAutoInputConfiguration
27
+ } from '@live-change/frontend-auto-form'
28
+
29
+ provideAutoViewComponents()
30
+ provideAutoInputConfiguration()
31
+ provideMetadataBasedAutoInputConfiguration()
32
+ ```
33
+
34
+ oraz dodajesz lokalizacje do i18n w `front/src/config.js`:
35
+
36
+ ```javascript
37
+ import { locales as autoFormLocales } from '@live-change/frontend-auto-form'
38
+
39
+ export default {
40
+ i18n: {
41
+ messages: {
42
+ en: deepmerge.all([
43
+ baseLocales.en,
44
+ autoFormLocales.en,
45
+ // ...
46
+ ]),
47
+ pl: deepmerge.all([
48
+ baseLocales.pl || {},
49
+ autoFormLocales.pl || {},
50
+ // ...
51
+ ])
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Kluczowe komponenty
58
+
59
+ #### `AutoField`
60
+
61
+ Generuje pojedyncze pole formularza na podstawie definicji właściwości modelu lub akcji:
62
+
63
+ ```vue
64
+ <AutoField
65
+ :definition="definition.properties.firstName"
66
+ v-model="editable.firstName"
67
+ :error="validationResult?.propertyErrors?.firstName"
68
+ :label="t('profile.firstName')"
69
+ />
70
+ ```
71
+
72
+ Możesz wstrzykiwać własny layout przez sloty (`#default`, `#label`, `#error`), jak w `ProfileSettings.vue` (`speed-dating`).
73
+
74
+ #### `AutoInput` i `AutoEditor`
75
+
76
+ `AutoInput` – pojedyncze pole edycyjne na podstawie definicji,
77
+
78
+ `AutoEditor` – edytor całego obiektu (modelu) na podstawie definicji:
79
+
80
+ ```vue
81
+ <auto-editor
82
+ :definition="eventDefinition"
83
+ v-model="editable"
84
+ :rootValue="editable"
85
+ i18n="event."
86
+ />
87
+ ```
88
+
89
+ W przykładzie z `speed-dating/front/src/pages/events/[event]/edit.vue`:
90
+
91
+ - `eventDefinition` pochodzi z `api.services.speedDating.models.Event`
92
+ - `editable` jest synchronizowane z widokiem i akcją poprzez `synchronized`
93
+
94
+ #### `ModelEditor`, `ModelView`, `ModelSingle`
95
+
96
+ Te komponenty łączą:
97
+
98
+ - definicję modelu
99
+ - widoki i akcje (DAO)
100
+ - generowane formularze i widoki list/szczegółów
101
+
102
+ Są używane m.in. w:
103
+
104
+ - `speed-dating/front/src/pages/events/[event]/surveys.vue` (`ModelEditor` dla ankiet)
105
+ - `family-tree` – ekrany ustawień i edytory szablonów
106
+
107
+ ### Flow od modelu do CRUD
108
+
109
+ 1. **Definicja modelu i akcji** w serwisie po stronie serwera (np. `Event`, `TreeSettings`).
110
+ 2. **Eksport metadanych** przez Live Change i odczyt przez `@live-change/vue3-ssr` (`api.services[service].models[Model]`).
111
+ 3. **Auto-form** generuje formularze i widoki:
112
+ - `AutoField` dla pojedynczych pól,
113
+ - `AutoEditor` / `ModelEditor` dla całych obiektów,
114
+ - `ActionForm` / `ActionEditor` dla akcji.
115
+
116
+ ### Przykłady z projektów
117
+
118
+ #### `family-tree`
119
+
120
+ - `front/src/components/TreeSettings.vue`:
121
+ - `AutoField` użyty do edycji zagnieżdżonych ustawień (rootPerson, format, marginesy, tło, tytuł)
122
+ - integracja z PrimeVue (Dropdown, Slider, InputNumber) przez sloty
123
+ - `front/src/components/marketing/*Editor.vue`:
124
+ - `AutoField`, `editorData` do edycji szablonów i obrazów reklamowych
125
+ - `front/src/pages/tree/[tree]/settings.vue`:
126
+ - użycie `editorData` z `@live-change/frontend-auto-form` do spięcia formularza z modelem ustawień
127
+
128
+ #### `speed-dating`
129
+
130
+ - `front/src/components/profile/ProfileSettings.vue`:
131
+ - `AutoField` dla pól identyfikacji użytkownika, z własnymi layoutami i walidacją
132
+ - integracja z `synchronized` i serwisem `draft`
133
+ - `front/src/pages/events/[event]/edit.vue`:
134
+ - `AutoInput`, `AutoField`, `AutoEditor` do edycji eventu
135
+ - `front/src/pages/events/[event]/surveys.vue`:
136
+ - `ModelEditor` do edycji ankiet powiązanych z eventem
137
+
138
+ Te pliki są najlepszym odniesieniem, jeśli chcesz szybko zobaczyć, jak układa się flow od modelu do działającego CRUD-a.
139
+
@@ -98,7 +98,7 @@
98
98
  params: {
99
99
  serviceName: service.value,
100
100
  modelName: model.value,
101
- id: objectData.value?.to ?? objectData.value?.id
101
+ id: objectData.value?.to ?? objectData.value?.id ?? object.value
102
102
  }
103
103
  }
104
104
  })
@@ -115,7 +115,7 @@
115
115
  params: {
116
116
  serviceName: service.value,
117
117
  modelName: model.value,
118
- id: objectData.value?.to ?? objectData.value?.id
118
+ id: objectData.value?.to ?? objectData.value?.id ?? object.value
119
119
  }
120
120
  }
121
121
  })
@@ -107,8 +107,8 @@
107
107
  required: true,
108
108
  },
109
109
  views: {
110
- type: Object,
111
- default: () => ({})
110
+ type: Array,
111
+ default: () => ([{}])
112
112
  },
113
113
  })
114
114
  const { service, model, views } = toRefs(props)
@@ -124,7 +124,7 @@
124
124
  }, AutoObjectIdentification)
125
125
  )
126
126
 
127
- import { useApi, usePath, live } from '@live-change/vue3-ssr'
127
+ import { useApi, usePath, live, view } from '@live-change/vue3-ssr'
128
128
  const api = useApi()
129
129
  const path = usePath()
130
130
 
@@ -6,7 +6,7 @@
6
6
  <pre>selectedPaths = {{ selectedPaths }}</pre>
7
7
  <pre>selectedPathWithElements = {{ selectedPathWithElements }}</pre> -->
8
8
  <div v-for="path in selectedPathsWithElements" :key="path">
9
- <Breadcrumb :model="more ? [...path, 'dupa'] : path"
9
+ <Breadcrumb :model="more ? [...path, ...pathObjects, 'placeholder'] : [...path, ...pathObjects]"
10
10
  :pt="{
11
11
  list: {
12
12
  class: 'text-sm flex flex-row flex-wrap gap-2 first:negative-margin-left pl-[1.5rem]'
@@ -46,9 +46,13 @@
46
46
  more: {
47
47
  type: Boolean,
48
48
  default: false
49
+ },
50
+ pathObjects: {
51
+ type: Array,
52
+ default: () => []
49
53
  }
50
54
  })
51
- const { objectType, object, more } = toRefs(props)
55
+ const { objectType, object, more, pathObjects } = toRefs(props)
52
56
 
53
57
  import { usePath, live } from '@live-change/vue3-ssr'
54
58
  const path = usePath()
@@ -87,7 +91,7 @@
87
91
  } else {
88
92
  elements.push({ objectType, object, propertyFrom: propertyName })
89
93
  }
90
- }
94
+ }
91
95
  return elements
92
96
  }
93
97
 
@@ -114,13 +118,13 @@
114
118
  live(pathsPath)
115
119
  ])
116
120
 
117
- const selectedPaths = computed(() => {
118
- return objectPathConfig.scopeSelector(paths.value)
119
- })
121
+ const selectedPaths = computed(() => objectPathConfig.scopeSelector(paths.value))
120
122
 
121
- const selectedPathsWithElements = computed(
122
- () => selectedPaths.value.map(scopePath => objectPathConfig.pathElements(scopePath))
123
- )
123
+ const selectedPathsWithElements = computed(() => {
124
+ const result = selectedPaths.value.map(scopePath => objectPathConfig.pathElements(scopePath))
125
+ if(result.length == 0) return [[{objectType: objectType.value, object: object.value}]]
126
+ return result
127
+ })
124
128
 
125
129
 
126
130
 
@@ -194,6 +194,7 @@ export default async function actionData(options) {
194
194
  mergedInitialValue,
195
195
  editableProperties,
196
196
  value: synchronizedData.value,
197
+ data: synchronizedData.value,
197
198
  changed,
198
199
  submit,
199
200
  submitting,
@@ -231,6 +232,7 @@ export default async function actionData(options) {
231
232
  initialValue,
232
233
  editableProperties,
233
234
  value: formData,
235
+ data: formData,
234
236
  changed,
235
237
  submit,
236
238
  submitting,
@@ -25,6 +25,7 @@ export default function editorData(options) {
25
25
  recursive = true,
26
26
  debounce = 600,
27
27
  timeField = 'lastUpdate',
28
+ crudSource = 'crud',
28
29
 
29
30
  savedToast = "Saved",
30
31
  savedDraftToast = "Draft saved",
@@ -56,7 +57,7 @@ export default function editorData(options) {
56
57
  const service = api.services[serviceName]
57
58
  const model = service.models[modelName]
58
59
  const {
59
- crudMethods = model.crud,
60
+ crudMethods = model[crudSource],
60
61
  identifiersNames = model.identifiers,
61
62
  editableProperties = model.editableProperties ?? Object.keys(model.properties),
62
63
  } = options
@@ -130,7 +131,8 @@ export default function editorData(options) {
130
131
  const savedIdentifiers = {}
131
132
  for(const identifier of identifiersNames) {
132
133
  if(typeof identifier === 'object') {
133
- savedIdentifiers[identifier.name] = savedData.value?.[identifier.name] ?? draftData.value?.data?.[identifier.name]
134
+ savedIdentifiers[identifier.name] = savedData.value?.[identifier.field] ?? savedData.value?.[identifier.name]
135
+ ?? draftData.value?.data?.[identifier.field] ?? draftData.value?.data?.[identifier.name]
134
136
  } else {
135
137
  savedIdentifiers[identifier] = savedData.value?.[identifier] ?? draftData.value?.data?.[identifier]
136
138
  }
@@ -209,7 +211,7 @@ export default function editorData(options) {
209
211
 
210
212
  const propertiesErrors = computed(() => propertiesValidationErrors(
211
213
  synchronizedData.value.value, identifiers, model, lastUploadedData.value,
212
- propertiesServerErrors.value, appContext))
214
+ propertiesServerErrors.value, appContext, editableProperties))
213
215
 
214
216
  async function save() {
215
217
  const saveResult = await saveData(synchronizedData.value.value)
@@ -240,6 +242,7 @@ export default function editorData(options) {
240
242
  return {
241
243
  identifiers,
242
244
  value: synchronizedData.value,
245
+ data: synchronizedData.value,
243
246
  changed,
244
247
  save,
245
248
  saving,
@@ -255,6 +258,8 @@ export default function editorData(options) {
255
258
  saved: savedData,
256
259
  savedPath: savedDataPath,
257
260
  editableSavedData,
261
+ editableDraftData,
262
+ defaultData: defaultData(model),
258
263
  draft: draftData,
259
264
  sourceChanged /// needed for draft discard on concurrent save
260
265
  }
@@ -281,7 +286,7 @@ export default function editorData(options) {
281
286
 
282
287
  const propertiesErrors = computed(() => propertiesValidationErrors(
283
288
  synchronizedData.value.value, identifiers, model, lastUploadedData.value,
284
- propertiesServerErrors.value, appContext))
289
+ propertiesServerErrors.value, appContext, editableProperties))
285
290
 
286
291
  async function reset() {
287
292
  synchronizedData.value.value = editableSavedData.value || deepmerge(defaultData(model), initialData)
@@ -292,11 +297,15 @@ export default function editorData(options) {
292
297
  return {
293
298
  identifiers,
294
299
  value: synchronizedData.value,
300
+ data: synchronizedData.value,
295
301
  changed: synchronizedData.changed,
296
302
  save: synchronizedData.save,
297
303
  saving: synchronizedData.saving,
298
304
  saved: savedData,
299
305
  savedPath: savedDataPath,
306
+ editableProperties,
307
+ editableSavedData,
308
+ defaultData: defaultData(model),
300
309
  isNew,
301
310
  propertiesErrors,
302
311
  reset,
@@ -196,7 +196,8 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
196
196
  const name = 'rangeBy_' + objectType
197
197
  const typeView = from.crud?.[name]
198
198
  ? name
199
- : undefined
199
+ : undefined
200
+
200
201
  if(typeView) {
201
202
  views.push({
202
203
  name: typeView,
@@ -205,18 +206,27 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
205
206
  }
206
207
  })
207
208
  } else {
208
- for(let i = 0; i < what.length; i++) {
209
- if(what[i] !== objectType) continue
210
- const propertyName = relationConfig.propertyNames?.[i]
211
- ?? model[0].toLowerCase() + model.slice(1)
212
- const name = 'rangeBy' + propertyName[0].toUpperCase() + propertyName.slice(1)
213
- if(!from.crud?.[name]) continue
209
+ if(singular) {
214
210
  views.push({
215
- name,
211
+ name: 'read',
216
212
  identifiers: {
217
- [propertyName]: object
213
+ [model[0].toLowerCase() + model.slice(1)]: object
218
214
  }
219
215
  })
216
+ } else {
217
+ for(let i = 0; i < what.length; i++) {
218
+ if(what[i] !== objectType) continue
219
+ const propertyName = relationConfig.propertyNames?.[i]
220
+ ?? model[0].toLowerCase() + model.slice(1)
221
+ const name = 'rangeBy' + propertyName[0].toUpperCase() + propertyName.slice(1)
222
+ if(!from.crud?.[name]) continue
223
+ views.push({
224
+ name,
225
+ identifiers: {
226
+ [propertyName]: object
227
+ }
228
+ })
229
+ }
220
230
  }
221
231
  }
222
232
  console.log(objectType, "VIEWS", views, "FROM", from, "SINGULAR", singular)
@@ -1,6 +1,6 @@
1
1
  import { validateData } from "@live-change/vue3-components"
2
2
 
3
- export function propertiesValidationErrors(rootValue, parameters, definition, lastData, propertiesServerErrors, appContext) {
3
+ export function propertiesValidationErrors(rootValue, parameters, definition, lastData, propertiesServerErrors, appContext, validatedProperties) {
4
4
  const currentValue = {
5
5
  ...parameters,
6
6
  ...rootValue,
@@ -10,10 +10,10 @@ export function propertiesValidationErrors(rootValue, parameters, definition, la
10
10
  lastData, propertiesServerErrors, appContext) */
11
11
 
12
12
  const validationResult = validateData(definition, currentValue, 'validation', appContext,
13
- '', rootValue, true)
13
+ '', rootValue, true, validatedProperties)
14
14
 
15
15
  const softValidationResult = validateData(definition, currentValue, 'softValidation', appContext,
16
- '', rootValue, true)
16
+ '', rootValue, true, validatedProperties)
17
17
 
18
18
  const serverValidationResult = {}
19
19
  if(propertiesServerErrors) {
@@ -28,7 +28,9 @@ export default async function viewData(options) {
28
28
 
29
29
  const savedDataPath = path[serviceName][crudMethods.read]({
30
30
  [modelName[0].toLowerCase() + modelName.slice(1)]: id
31
- })
31
+ })
32
+
33
+ console.log("SAVED DATA PATH", savedDataPath)
32
34
 
33
35
  let data
34
36
  let error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/frontend-auto-form",
3
- "version": "0.9.198",
3
+ "version": "0.9.200",
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.198",
26
- "@live-change/dao": "^0.9.198",
27
- "@live-change/dao-vue3": "^0.9.198",
28
- "@live-change/dao-websocket": "^0.9.198",
29
- "@live-change/framework": "^0.9.198",
30
- "@live-change/image-frontend": "^0.9.198",
31
- "@live-change/image-service": "^0.9.198",
32
- "@live-change/session-service": "^0.9.198",
33
- "@live-change/vue3-components": "^0.9.198",
34
- "@live-change/vue3-ssr": "^0.9.198",
25
+ "@live-change/cli": "^0.9.200",
26
+ "@live-change/dao": "^0.9.200",
27
+ "@live-change/dao-vue3": "^0.9.200",
28
+ "@live-change/dao-websocket": "^0.9.200",
29
+ "@live-change/framework": "^0.9.200",
30
+ "@live-change/image-frontend": "^0.9.200",
31
+ "@live-change/image-service": "^0.9.200",
32
+ "@live-change/session-service": "^0.9.200",
33
+ "@live-change/vue3-components": "^0.9.200",
34
+ "@live-change/vue3-ssr": "^0.9.200",
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.7"
53
53
  },
54
54
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "^0.9.198",
55
+ "@live-change/codeceptjs-helper": "^0.9.200",
56
56
  "codeceptjs": "^3.7.6",
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": "7e485dcbaa2af7fb17052a40238210dc8bdf0c09"
66
+ "gitHead": "a509834e600a546297faa7d1534b6f52e66d2e66"
67
67
  }