@live-change/frontend-auto-form 0.9.7 → 0.9.9

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.
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <template v-if="typeof identificationConfig === 'string'">
3
+ <span><i class="pi pi-box mr-1"></i>{{ objectData[identificationConfig] }}</span>
4
+ </template>
5
+ <template v-else>
6
+ <span>
7
+ <i class="pi pi-box mr-1"></i>
8
+ <strong>{{ objectType }}</strong>: {{ object ?? objectData.to ?? objectData.id }}
9
+ </span>
10
+ </template>
11
+ </template>
12
+
13
+ <script setup>
14
+
15
+ import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
16
+
17
+ const props = defineProps({
18
+ objectType: {
19
+ type: String,
20
+ required: true
21
+ },
22
+ object: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ data: {
27
+ type: Object,
28
+ default: null
29
+ },
30
+ inline: {
31
+ type: Boolean,
32
+ default: false
33
+ }
34
+ })
35
+ const { objectType, object, data, inline } = toRefs(props)
36
+
37
+ import { useApi, usePath, live } from '@live-change/vue3-ssr'
38
+ const api = useApi()
39
+ const path = usePath()
40
+
41
+ const serviceAndModel = computed(() => {
42
+ const [service, model] = objectType.value.split('_')
43
+ return { service, model }
44
+ })
45
+ const service = computed(() => serviceAndModel.value.service)
46
+ const model = computed(() => serviceAndModel.value.model)
47
+
48
+ const modelDefinition = computed(() => {
49
+ return api.services?.[service.value]?.models?.[model.value]
50
+ })
51
+
52
+ const identificationViewName = computed(() => {
53
+ return modelDefinition.value?.crud?.identification || modelDefinition.value?.crud?.read
54
+ })
55
+
56
+ const identificationConfig = computed(() => {
57
+ return modelDefinition.value?.identification
58
+ })
59
+
60
+ const objectDataPath = computed(() => {
61
+ if(data.value) return null
62
+ if(!identificationConfig.value) return null
63
+ const viewName = identificationViewName.value
64
+ if(!viewName) return null
65
+ const modelName = model.value
66
+ return path[service.value][viewName]({
67
+ [modelName[0].toLowerCase() + modelName.slice(1)]: object.value
68
+ })
69
+ })
70
+
71
+ const loadedObjectData = await live(objectDataPath)
72
+ const objectData = computed(() => data.value || loadedObjectData.value)
73
+
74
+
75
+ </script>
76
+
77
+ <style scoped>
78
+
79
+ </style>
@@ -1,32 +1,30 @@
1
1
  <template>
2
- <div class="flex flex-row justify-content-between align-items-center mt-3">
3
- <div class="flex flex-column">
4
- <div v-if="draftChanged" class="text-sm text-500 mr-2">
5
- Draft changed
6
- </div>
7
- <div v-if="savingDraft" class="text-500 mr-2">
8
- <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
2
+ <div class="flex flex-column-reverse md:flex-row justify-content-between align-items-center mt-3">
3
+ <div class="flex flex-column mt-2 md:mt-0">
4
+ <div v-if="savingDraft" class="text-500 mr-2 flex flex-row align-items-center">
5
+ <i class="pi pi-spin pi-spinner mr-2" style="font-size: 1.23rem"></i>
9
6
  <span>Saving draft...</span>
10
7
  </div>
11
- <div v-if="validationResult" class="font-semibold p-error mr-2">
8
+ <div v-else-if="draftChanged" class="text-sm text-500 mr-2">
9
+ Draft changed
10
+ </div>
11
+ <div v-else-if="validationResult" class="font-semibold p-error mr-2">
12
12
  Before saving, please correct the errors above.
13
13
  </div>
14
+ <div v-else-if="!changed" class="">
15
+ No changes to save.
16
+ </div>
14
17
  </div>
15
18
  <div class="flex flex-row">
16
19
  <slot name="submit" v-if="!validationResult">
17
- <div v-if="changed" class="ml-2">
18
- <Button type="submit" label="Save" class="" icon="pi pi-save" />
19
- </div>
20
- <div v-else class="flex flex-row align-items-center ml-2">
21
- <div class="mr-2">
22
- No changes to save.
23
- </div>
24
- <Button type="submit" label="Save" class="" icon="pi pi-save" disabled />
20
+ <div class="ml-2">
21
+ <Button v-if="exists" type="submit" :label="'Save '+model.name" :disabled="!changed" icon="pi pi-save" />
22
+ <Button v-else type="submit" :label="'Create '+model.name" :disabled="!changed" icon="pi pi-sparkles" />
25
23
  </div>
26
24
  </slot>
27
25
  <slot name="reset" v-if="resetButton">
28
- <div v-if="changed">
29
- <Button label="Reset" class="ml-2" icon="pi pi-eraser" @click="editor.reset" />
26
+ <div>
27
+ <Button type="reset" label="Reset" class="ml-2" :disabled="!changed" icon="pi pi-eraser"/>
30
28
  </div>
31
29
  </slot>
32
30
  </div>
@@ -61,7 +59,7 @@
61
59
  const model = computed(() => editor.value.model)
62
60
 
63
61
  const changed = computed(() => editor.value.changed.value)
64
-
62
+ const exists = computed(() => !!editor.value.saved.value)
65
63
  const draftChanged = computed(() => editor.value.draftChanged?.value)
66
64
  const savingDraft = computed(() => editor.value.savingDraft?.value)
67
65
  const sourceChanged = computed(() => editor.value.sourceChanged?.value)
@@ -1,13 +1,29 @@
1
1
  <template>
2
2
  <div>
3
- <div v-if="editor">
3
+ <!--
4
+ <h4>identifiers as object</h4>
5
+ <pre>{{ identifiersObject }}</pre>
6
+
7
+ <h4>definition</h4>
8
+ <pre>{{ modelDefinition }}</pre>-->
9
+
10
+ <div class="">
11
+ Service <strong>{{ service }}</strong>
12
+ </div>
13
+ <div class="text-2xl mb-4">
14
+ <span v-if="isNew">Create </span>
15
+ <span v-else>Edit </span>
16
+ <strong>{{ model }}</strong>
17
+ </div>
18
+
19
+ <form v-if="editor" @submit="handleSave" @reset="handleReset">
4
20
  <auto-editor
5
21
  :definition="modelDefinition"
6
22
  v-model="editor.value.value"
7
23
  :rootValue="editor.value.value"
8
24
  :i18n="i18n" />
9
25
  <EditorButtons :editor="editor" reset-button />
10
- </div>
26
+ </form>
11
27
  </div>
12
28
  </template>
13
29
 
@@ -68,7 +84,7 @@
68
84
  draft: draft.value,
69
85
  autoSave: true,
70
86
  ...options.value,
71
- onSaved: (...args) => emit('saved', ...args),
87
+ onSaved: (...args) => handleSaved(...args),
72
88
  onDraftSaved: (...args) => emit('draftSaved', ...args),
73
89
  onDraftDiscarded: (...args) => emit('draftDiscarded', ...args),
74
90
  onSaveError: (...args) => emit('saveError', ...args),
@@ -82,10 +98,25 @@
82
98
  }
83
99
  })
84
100
 
85
- const isNew = computed(() => editor.value.saved.value)
101
+ const isNew = computed(() => !editor?.value?.saved?.value)
102
+
103
+ function handleSave(ev) {
104
+ ev.preventDefault()
105
+ editor.value.save()
106
+ }
86
107
 
87
- /// TODO: detect when we should change identifiers
88
- // const savedIdentifiers
108
+ function handleReset(ev) {
109
+ ev.preventDefault()
110
+ editor.value.reset()
111
+ }
112
+
113
+ function handleSaved(saveResult) {
114
+ console.log("SAVED", saveResult, isNew.value, editor.value.isNew)
115
+ if(saveResult && isNew.value && editor.value.isNew) {
116
+ emit('created', saveResult)
117
+ }
118
+ emit('saved', saveResult)
119
+ }
89
120
 
90
121
  </script>
91
122
 
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <div class="w-full lg:w-8 md:w-11">
3
+ <!-- <h4>definition</h4>
4
+ <pre>{{ modelDefinition }}</pre>-->
5
+
6
+ <div class="surface-card w-full p-3 shadow-1 border-round mb-2">
7
+ <div class="">
8
+ Service <strong>{{ service }}</strong>
9
+ </div>
10
+ <div class="text-2xl">
11
+ <strong>{{ pluralize(model) }}</strong>
12
+ <span class="ml-1">list</span>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="surface-card p-3 shadow-1 border-round">
17
+ <range-viewer :key="JSON.stringify(modelsPathRangeConfig)"
18
+ :pathFunction="modelsPathRangeFunction"
19
+ :canLoadTop="false" canDropBottom
20
+ loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
21
+ <template #empty>
22
+ <div class="text-xl text-800 my-3 mx-3">
23
+ No {{ pluralize(model) }} found
24
+ </div>
25
+ </template>
26
+ <template #default="{ item: object }">
27
+ <div class="flex flex-row align-items-center justify-content-between">
28
+ <ObjectIdentification
29
+ :objectType="service + '_' + model"
30
+ :object="object.to ?? object.id"
31
+ :data="object"
32
+ />
33
+ <div class="flex flex-row">
34
+ <router-link :to="editRoute(object)" class="no-underline">
35
+ <Button icon="pi pi-pencil" severity="primary" label="Edit" class="mr-2" />
36
+ </router-link>
37
+
38
+ <Button icon="pi pi-eraser" severity="primary" label="Delete" class="mr-2" />
39
+ </div>
40
+ </div>
41
+ </template>
42
+ </range-viewer>
43
+ </div>
44
+
45
+ <div class="mt-3 flex flex-row justify-content-end mr-2">
46
+ <router-link :to="createRoute" class="no-underline2">
47
+ <Button icon="pi pi-plus" :label="'Create new '+model" />
48
+ </router-link>
49
+ </div>
50
+
51
+
52
+ </div>
53
+ </template>
54
+
55
+ <script setup>
56
+
57
+ import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
58
+ import { RangeViewer, injectComponent } from "@live-change/vue3-components"
59
+ import pluralize from 'pluralize'
60
+
61
+ const props = defineProps({
62
+ service: {
63
+ type: String,
64
+ required: true,
65
+ },
66
+ model: {
67
+ type: String,
68
+ required: true,
69
+ }
70
+ })
71
+ const { service, model } = toRefs(props)
72
+
73
+ import AutoObjectIdentification from './AutoObjectIdentification.vue'
74
+
75
+ const ObjectIdentification = computed(() =>
76
+ injectComponent({
77
+ name: 'ObjectIdentification',
78
+ type: service.value + '_' + model.value,
79
+ service: service.value,
80
+ model: model.value
81
+ }, AutoObjectIdentification)
82
+ )
83
+
84
+ import { useApi, usePath, live, reverseRange } from '@live-change/vue3-ssr'
85
+ const api = useApi()
86
+ const path = usePath()
87
+
88
+ const modelDefinition = computed(() => {
89
+ return api.services?.[service.value]?.models?.[model.value]
90
+ })
91
+
92
+ const modelsPathRangeConfig = computed(() => {
93
+ return {
94
+ service: service.value,
95
+ model: model.value,
96
+ definition: modelDefinition.value,
97
+ reverse: true
98
+ }
99
+ })
100
+ const modelsPathRangeFunction = computed(() => {
101
+ const config = modelsPathRangeConfig.value
102
+ const rangeView = config.definition?.crud?.range
103
+ return (range) => path[config.service][rangeView]({
104
+ ...(config.reverse ? reverseRange(range) : range),
105
+ })
106
+ })
107
+
108
+ function objectIdentifiers(object) {
109
+ const identifiers = {}
110
+ for(const identifierDefinition of modelDefinition.value.identifiers) {
111
+ if(typeof identifierDefinition === 'string') {
112
+ identifiers[identifierDefinition] = object[identifierDefinition]
113
+ } else {
114
+ if(identifierDefinition.field === 'id') {
115
+ identifiers[identifierDefinition.name] = object?.to ?? object.id
116
+ } else {
117
+ identifiers[identifierDefinition.name] = object[identifierDefinition.field]
118
+ }
119
+ }
120
+ }
121
+ return identifiers
122
+ }
123
+
124
+ function editRoute(object) {
125
+ return {
126
+ name: 'auto-form:editor',
127
+ params: {
128
+ serviceName: service.value,
129
+ modelName: model.value,
130
+ identifiers: Object.values(objectIdentifiers(object))
131
+ }
132
+ }
133
+ }
134
+
135
+ const createRoute = computed(() => ({
136
+ name: 'auto-form:editor',
137
+ params: {
138
+ serviceName: service.value,
139
+ modelName: model.name
140
+ }
141
+ }))
142
+
143
+ </script>
144
+
145
+ <style scoped>
146
+
147
+ </style>
@@ -59,12 +59,8 @@ export default function editorData(options) {
59
59
  let draftIdParts = []
60
60
  for(const identifier of identifiersNames) {
61
61
  if(typeof identifier === 'object') {
62
- for(const [key, value] of Object.entries(identifier)) {
63
- if(value === 'id') {
64
- idKey = key
65
- }
66
- draftIdParts.push(value)
67
- }
62
+ if(identifier.field === 'id') idKey = identifier.name
63
+ draftIdParts.push('id')
68
64
  } else {
69
65
  draftIdParts.push(identifier)
70
66
  }
@@ -110,6 +106,9 @@ export default function editorData(options) {
110
106
  saving.value = true
111
107
  savePromise = (async () => {
112
108
  try {
109
+ if(createOrUpdateAction) {
110
+ return createOrUpdateAction(requestData)
111
+ }
113
112
  if(savedData.value) {
114
113
  return updateAction(requestData)
115
114
  } else {
@@ -124,7 +123,7 @@ export default function editorData(options) {
124
123
  })()
125
124
  if(!autoSave && workingZone)
126
125
  workingZone.addPromise('save:'+serviceName+':'+modelName, savePromise.catch(() => {}))
127
- await savePromise
126
+ return await savePromise
128
127
  }
129
128
 
130
129
  if(draft) {
@@ -140,6 +139,7 @@ export default function editorData(options) {
140
139
  autoSave: true,
141
140
  debounce,
142
141
  timeField,
142
+ isNew,
143
143
  resetOnError: false,
144
144
  onSave: () => {
145
145
  onDraftSaved()
@@ -158,9 +158,9 @@ export default function editorData(options) {
158
158
  JSON.stringify(draftData.value.from) !== JSON.stringify(editableSavedData.value))
159
159
 
160
160
  async function save() {
161
- await saveData(synchronizedData.value.value)
161
+ const saveResult = await saveData(synchronizedData.value.value)
162
162
  if(draftData.value) await removeDraftAction(draftIdentifiers)
163
- onSaved()
163
+ onSaved(saveResult)
164
164
  if(toast && savedToast) toast.add({ severity: 'success', summary: savedToast, life: 1500 })
165
165
  }
166
166
 
@@ -191,6 +191,7 @@ export default function editorData(options) {
191
191
  reset,
192
192
  discardDraft,
193
193
  model,
194
+ isNew,
194
195
  resetOnError: false,
195
196
  draftChanged: synchronizedData.changed,
196
197
  saveDraft: synchronizedData.save,
@@ -202,14 +203,14 @@ export default function editorData(options) {
202
203
  } else {
203
204
  const synchronizedData = synchronized({
204
205
  source,
205
- update: createOrUpdateAction ?? saveData,
206
+ update: saveData,
206
207
  updateDataProperty,
207
208
  identifiers,
208
209
  recursive,
209
210
  autoSave,
210
211
  debounce,
211
- onSave: () => {
212
- onSaved()
212
+ onSave: (result) => {
213
+ onSaved(result)
213
214
  if(toast && savedToast) toast.add({ severity: 'success', summary: savedToast, life: 1500 })
214
215
  },
215
216
  onSaveError(e) {
@@ -2,17 +2,8 @@
2
2
  <div class="w-full lg:w-8 md:w-11">
3
3
  <div class="surface-card p-3 shadow-1 border-round">
4
4
 
5
- <div class="text-xl mb-2">
6
- Service <strong>{{ serviceName }} model {{ modelName }}</strong>
7
- </div>
8
-
9
- <h4>identifiers as object</h4>
10
- <pre>{{ identifiersObject }}</pre>
11
-
12
- <h4>definition</h4>
13
- <pre>{{ modelDefinition }}</pre>
14
-
15
- <ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft />
5
+ <ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft
6
+ @created="handleCreated"/>
16
7
 
17
8
  </div>
18
9
  </div>
@@ -22,6 +13,8 @@
22
13
 
23
14
  import ModelEditor from "../components/crud/ModelEditor.vue"
24
15
 
16
+
17
+
25
18
  import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
26
19
 
27
20
  const props = defineProps({
@@ -59,15 +52,34 @@
59
52
  if(typeof identifierDefinition === 'string') {
60
53
  result[identifierDefinition] = identifier
61
54
  } else {
62
- result[Object.keys(identifierDefinition)[0]] = identifier
55
+ result[identifierDefinition.name] = identifier
63
56
  }
64
57
  }
65
58
  return result
66
59
  })
67
60
 
68
- function handleCreated() {
69
- console.log("CREATED")
70
- // TODO: change route - add identifiers
61
+ import { useRouter } from 'vue-router'
62
+ const router = useRouter()
63
+
64
+ function handleCreated(id) {
65
+ const newIdentifiers = modelDefinition.value.identifiers.map((identifier, i) => {
66
+ if(typeof identifier === 'object' && identifier.field === 'id') {
67
+ return id
68
+ }
69
+ return identifiers.value[i]
70
+ })
71
+
72
+ //console.log("newIdentifiers", newIdentifiers)
73
+ if(JSON.stringify(identifiers.value) !== JSON.stringify(newIdentifiers)) {
74
+ router.push({
75
+ name: 'auto-form:editor',
76
+ params: {
77
+ serviceName: serviceName.value,
78
+ modelName: modelName.value,
79
+ identifiers: newIdentifiers
80
+ }
81
+ })
82
+ }
71
83
  }
72
84
 
73
85
  </script>
@@ -1,12 +1,9 @@
1
1
  <template>
2
- <div class="w-full lg:w-8 md:w-11">
3
- <div class="surface-card p-3 shadow-1 border-round">
4
- List
5
- </div>
6
- </div>
2
+ <ModelList :service="serviceName" :model="modelName" />
7
3
  </template>
8
4
 
9
5
  <script setup>
6
+ import ModelList from "../components/crud/ModelList.vue"
10
7
 
11
8
  import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
12
9
 
@@ -18,21 +15,14 @@
18
15
  modelName: {
19
16
  type: String,
20
17
  required: true,
21
- },
22
- identifiers: {
23
- type: Array,
24
- default: []
25
18
  }
26
19
  })
27
- const { serviceName } = toRefs(props)
20
+ const { serviceName, modelName } = toRefs(props)
28
21
 
29
22
  import { useApi, usePath, live } from '@live-change/vue3-ssr'
30
23
  const api = useApi()
31
24
  const path = usePath()
32
25
 
33
-
34
-
35
-
36
26
  </script>
37
27
 
38
28
  <style scoped>
@@ -10,14 +10,16 @@
10
10
  <div class="text-xl flex flex-row align-items-center mr-4">
11
11
  <strong>{{ model.name }}</strong>
12
12
  <span class="mx-1">model</span>
13
- <div v-for="relation of model.relations" class="">
13
+ <div v-for="relation of model.relations">
14
14
  <span class="mr-1">-</span>
15
15
  <span>{{ relation.name }}</span>
16
16
  <!-- <span v-if="relation.config.what"></span>-->
17
17
  </div>
18
18
  </div>
19
- <div>
20
- <Button icon="pi pi-list" severity="primary" label="List" class="mr-2" />
19
+ <div class="mt-2 md:mt-0">
20
+ <router-link :to="listRoute(serviceWithModels.name, model)" class="no-underline">
21
+ <Button icon="pi pi-list" severity="primary" label="List" class="mr-2" />
22
+ </router-link>
21
23
  <router-link :to="createRoute(serviceWithModels.name, model)" class="no-underline">
22
24
  <Button icon="pi pi-plus" severity="warning" :label="'Create new '+model.name" />
23
25
  </router-link>
@@ -79,6 +81,16 @@
79
81
  }
80
82
  }
81
83
 
84
+ function listRoute(serviceName, model) {
85
+ return {
86
+ name: 'auto-form:list',
87
+ params: {
88
+ serviceName,
89
+ modelName: model.name,
90
+ }
91
+ }
92
+ }
93
+
82
94
  </script>
83
95
 
84
96
  <style scoped>
@@ -14,6 +14,13 @@ export function autoFormRoutes(config = {}) {
14
14
  props: true
15
15
  }),
16
16
 
17
+ route({
18
+ name: 'auto-form:list', path: prefix + '/models/:serviceName/:modelName', meta: { },
19
+ component: () => import("./pages/List.vue"),
20
+ props: true
21
+ }),
22
+
23
+
17
24
  ]
18
25
  }
19
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/frontend-auto-form",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
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.5.2",
25
- "@live-change/cli": "^0.9.7",
26
- "@live-change/dao": "^0.9.7",
27
- "@live-change/dao-vue3": "^0.9.7",
28
- "@live-change/dao-websocket": "^0.9.7",
29
- "@live-change/framework": "^0.9.7",
30
- "@live-change/image-frontend": "^0.9.7",
31
- "@live-change/image-service": "^0.9.7",
32
- "@live-change/session-service": "^0.9.7",
33
- "@live-change/vue3-components": "^0.9.7",
34
- "@live-change/vue3-ssr": "^0.9.7",
25
+ "@live-change/cli": "^0.9.9",
26
+ "@live-change/dao": "^0.9.9",
27
+ "@live-change/dao-vue3": "^0.9.9",
28
+ "@live-change/dao-websocket": "^0.9.9",
29
+ "@live-change/framework": "^0.9.9",
30
+ "@live-change/image-frontend": "^0.9.9",
31
+ "@live-change/image-service": "^0.9.9",
32
+ "@live-change/session-service": "^0.9.9",
33
+ "@live-change/vue3-components": "^0.9.9",
34
+ "@live-change/vue3-ssr": "^0.9.9",
35
35
  "@vueuse/core": "^10.11.0",
36
36
  "codeceptjs-assert": "^0.0.5",
37
37
  "compression": "^1.7.4",
@@ -52,7 +52,7 @@
52
52
  "vue3-scroll-border": "0.1.6"
53
53
  },
54
54
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "^0.9.7",
55
+ "@live-change/codeceptjs-helper": "^0.9.9",
56
56
  "codeceptjs": "^3.6.5",
57
57
  "generate-password": "1.7.1",
58
58
  "playwright": "1.48.1",
@@ -63,5 +63,5 @@
63
63
  "author": "Michał Łaszczewski <michal@laszczewski.pl>",
64
64
  "license": "ISC",
65
65
  "description": "",
66
- "gitHead": "f936468bf39ea3c5b07ce14666f4b3a4a4a9287d"
66
+ "gitHead": "150ef8008399a3b6a160985a4a8d4e91255c0e99"
67
67
  }